consumer模块完成90%,前端完成supabase对接

This commit is contained in:
2026-02-03 17:11:50 +08:00
parent b6200cda28
commit 8a535e3f38
69 changed files with 5020 additions and 33273 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,692 +0,0 @@
-- =====================================================================================
-- 商城系统增量升级脚本 (ALTER方式)
-- 用于在现有数据库基础上添加商城功能
-- 表名前缀: ml_ (mall)
-- 复用表: ak_users (用户主表)
-- 兼容: PostgreSQL + Supabase
-- =====================================================================================
-- =====================================================================================
-- 1. 启用必要的扩展
-- =====================================================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
CREATE EXTENSION IF NOT EXISTS "btree_gin";
-- =====================================================================================
-- 2. 检查并创建商城核心表(如果不存在)
-- =====================================================================================
-- 商城用户扩展信息表
CREATE TABLE IF NOT EXISTS public.ml_user_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID UNIQUE NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
user_type INTEGER DEFAULT 1 NOT NULL,
status INTEGER DEFAULT 1 NOT NULL,
real_name VARCHAR(100),
id_card VARCHAR(32),
business_license VARCHAR(100),
credit_score INTEGER DEFAULT 100,
verification_status INTEGER DEFAULT 0,
verification_data JSONB DEFAULT '{}',
preferences JSONB DEFAULT '{}',
emergency_contact VARCHAR(200),
service_areas JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_user_type CHECK (user_type IN (1,2,3,4,5)),
CONSTRAINT chk_ml_user_status CHECK (status IN (1,2,3,4)),
CONSTRAINT chk_ml_verification_status CHECK (verification_status IN (0,1,2)),
CONSTRAINT chk_ml_credit_score CHECK (credit_score >= 0 AND credit_score <= 1000)
);
-- 用户地址表
CREATE TABLE IF NOT EXISTS public.ml_user_addresses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
receiver_name VARCHAR(100) NOT NULL,
receiver_phone VARCHAR(32) NOT NULL,
province VARCHAR(100) NOT NULL,
city VARCHAR(100) NOT NULL,
district VARCHAR(100) NOT NULL,
street VARCHAR(200),
address_detail TEXT NOT NULL,
postal_code VARCHAR(16),
is_default BOOLEAN DEFAULT FALSE,
label VARCHAR(50),
latitude DECIMAL(10,7),
longitude DECIMAL(10,7),
delivery_instructions TEXT,
business_hours VARCHAR(100),
status INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_address_status CHECK (status IN (1,2))
);
-- 商品分类表
CREATE TABLE IF NOT EXISTS public.ml_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL,
parent_id UUID REFERENCES public.ml_categories(id),
name VARCHAR(200) NOT NULL,
slug VARCHAR(200) UNIQUE,
description TEXT,
icon_url TEXT,
banner_url TEXT,
sort_order INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
path TEXT[],
is_active BOOLEAN DEFAULT TRUE,
seo_title VARCHAR(200),
seo_description VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 品牌表
CREATE TABLE IF NOT EXISTS public.ml_brands (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
logo_url TEXT,
description TEXT,
website VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 商品表
CREATE TABLE IF NOT EXISTS public.ml_products (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL,
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
category_id UUID NOT NULL REFERENCES public.ml_categories(id),
brand_id UUID REFERENCES public.ml_brands(id),
product_code VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(500) NOT NULL,
subtitle VARCHAR(1000),
description TEXT,
main_image_url TEXT,
image_urls JSONB DEFAULT '[]',
video_urls JSONB DEFAULT '[]',
base_price DECIMAL(12,2) NOT NULL CHECK (base_price >= 0),
market_price DECIMAL(12,2),
cost_price DECIMAL(12,2),
total_stock INTEGER DEFAULT 0 CHECK (total_stock >= 0),
available_stock INTEGER DEFAULT 0 CHECK (available_stock >= 0),
min_order_qty INTEGER DEFAULT 1 CHECK (min_order_qty > 0),
max_order_qty INTEGER,
weight DECIMAL(10,3),
dimensions JSONB,
status INTEGER DEFAULT 1,
is_featured BOOLEAN DEFAULT FALSE,
is_new BOOLEAN DEFAULT FALSE,
is_hot BOOLEAN DEFAULT FALSE,
view_count INTEGER DEFAULT 0,
sale_count INTEGER DEFAULT 0,
favorite_count INTEGER DEFAULT 0,
rating_avg DECIMAL(3,2) DEFAULT 0.00 CHECK (rating_avg >= 0 AND rating_avg <= 5),
rating_count INTEGER DEFAULT 0,
seo_title VARCHAR(200),
seo_description VARCHAR(500),
seo_keywords TEXT[],
slug VARCHAR(200) UNIQUE,
tags TEXT[],
attributes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
published_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT chk_ml_product_status CHECK (status IN (1,2,3,4))
);
-- 商品SKU表
CREATE TABLE IF NOT EXISTS public.ml_product_skus (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
sku_code VARCHAR(100) UNIQUE NOT NULL,
specifications JSONB DEFAULT '{}',
price DECIMAL(12,2) NOT NULL CHECK (price >= 0),
market_price DECIMAL(12,2),
cost_price DECIMAL(12,2),
stock INTEGER DEFAULT 0 CHECK (stock >= 0),
warning_stock INTEGER DEFAULT 10,
image_url TEXT,
weight DECIMAL(10,3),
status INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_sku_status CHECK (status IN (1,2))
);
-- 店铺信息表
CREATE TABLE IF NOT EXISTS public.ml_shops (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL,
merchant_id UUID UNIQUE NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
shop_name VARCHAR(200) NOT NULL,
shop_logo TEXT,
shop_banner TEXT,
description TEXT,
business_license VARCHAR(100),
contact_name VARCHAR(100),
contact_phone VARCHAR(32),
contact_email VARCHAR(200),
address JSONB,
business_hours JSONB,
status INTEGER DEFAULT 1,
product_count INTEGER DEFAULT 0,
order_count INTEGER DEFAULT 0,
rating_avg DECIMAL(3,2) DEFAULT 0.00,
rating_count INTEGER DEFAULT 0,
verified_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_shop_status CHECK (status IN (1,2,3))
);
-- 订单表
CREATE TABLE IF NOT EXISTS public.ml_orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL,
order_no VARCHAR(50) UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES public.ak_users(id),
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
product_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(12,2) DEFAULT 0,
shipping_fee DECIMAL(12,2) DEFAULT 0,
total_amount DECIMAL(12,2) NOT NULL,
paid_amount DECIMAL(12,2) DEFAULT 0,
shipping_address JSONB NOT NULL,
order_status INTEGER DEFAULT 1,
payment_status INTEGER DEFAULT 1,
shipping_status INTEGER DEFAULT 1,
paid_at TIMESTAMP WITH TIME ZONE,
shipped_at TIMESTAMP WITH TIME ZONE,
delivered_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
remark TEXT,
merchant_memo TEXT,
cancel_reason TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_order_status CHECK (order_status IN (1,2,3,4,5,6,7)),
CONSTRAINT chk_ml_payment_status CHECK (payment_status IN (1,2,3,4)),
CONSTRAINT chk_ml_shipping_status CHECK (shipping_status IN (1,2,3,4))
);
-- 购物车表
CREATE TABLE IF NOT EXISTS public.ml_shopping_cart (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
sku_id UUID REFERENCES public.ml_product_skus(id) ON DELETE CASCADE,
quantity INTEGER NOT NULL CHECK (quantity > 0),
selected BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, product_id, sku_id)
);
-- =====================================================================================
-- 3. ALTER 语句:为现有表添加商城相关字段
-- =====================================================================================
-- 为 ak_users 表添加商城相关字段(如果不存在)
DO $$
BEGIN
-- 添加商城相关字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_status') THEN
ALTER TABLE public.ak_users ADD COLUMN mall_status INTEGER DEFAULT 1; -- 1:正常 2:禁用
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_type') THEN
ALTER TABLE public.ak_users ADD COLUMN mall_type INTEGER DEFAULT 1; -- 1:消费者 2:商家
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'last_login_ip') THEN
ALTER TABLE public.ak_users ADD COLUMN last_login_ip INET;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_orders') THEN
ALTER TABLE public.ak_users ADD COLUMN total_orders INTEGER DEFAULT 0;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_spent') THEN
ALTER TABLE public.ak_users ADD COLUMN total_spent DECIMAL(12,2) DEFAULT 0.00;
END IF;
RAISE NOTICE 'ak_users 表字段添加完成';
END $$;
-- =====================================================================================
-- 4. 创建索引
-- =====================================================================================
-- 用户扩展表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_user_id ON public.ml_user_profiles(user_id);
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_type ON public.ml_user_profiles(user_type);
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_status ON public.ml_user_profiles(status);
-- 分类表索引
CREATE INDEX IF NOT EXISTS idx_ml_categories_cid ON public.ml_categories(cid);
CREATE INDEX IF NOT EXISTS idx_ml_categories_parent ON public.ml_categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_ml_categories_slug ON public.ml_categories(slug);
CREATE INDEX IF NOT EXISTS idx_ml_categories_level ON public.ml_categories(level, sort_order);
-- 品牌表索引
CREATE INDEX IF NOT EXISTS idx_ml_brands_cid ON public.ml_brands(cid);
CREATE INDEX IF NOT EXISTS idx_ml_brands_name ON public.ml_brands(name);
-- 地址表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_addresses_user_id ON public.ml_user_addresses(user_id);
CREATE INDEX IF NOT EXISTS idx_ml_user_addresses_default ON public.ml_user_addresses(user_id, is_default);
-- 商品表索引
CREATE INDEX IF NOT EXISTS idx_ml_products_cid ON public.ml_products(cid);
CREATE INDEX IF NOT EXISTS idx_ml_products_merchant ON public.ml_products(merchant_id, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_category ON public.ml_products(category_id, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_status ON public.ml_products(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_products_featured ON public.ml_products(is_featured, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_price ON public.ml_products(base_price);
CREATE INDEX IF NOT EXISTS idx_ml_products_rating ON public.ml_products(rating_avg DESC, rating_count DESC);
CREATE INDEX IF NOT EXISTS idx_ml_products_tags ON public.ml_products USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_ml_products_slug ON public.ml_products(slug);
-- 店铺表索引
CREATE INDEX IF NOT EXISTS idx_ml_shops_cid ON public.ml_shops(cid);
CREATE INDEX IF NOT EXISTS idx_ml_shops_merchant ON public.ml_shops(merchant_id);
-- SKU表索引
CREATE INDEX IF NOT EXISTS idx_ml_product_skus_product ON public.ml_product_skus(product_id);
CREATE INDEX IF NOT EXISTS idx_ml_product_skus_code ON public.ml_product_skus(sku_code);
-- 订单表索引
CREATE INDEX IF NOT EXISTS idx_ml_orders_cid ON public.ml_orders(cid);
CREATE INDEX IF NOT EXISTS idx_ml_orders_user ON public.ml_orders(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_merchant ON public.ml_orders(merchant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_status ON public.ml_orders(order_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_no ON public.ml_orders(order_no);
-- 购物车表索引
CREATE INDEX IF NOT EXISTS idx_ml_shopping_cart_user ON public.ml_shopping_cart(user_id);
-- ak_users 表新增字段索引
CREATE INDEX IF NOT EXISTS idx_ak_users_mall_status ON public.ak_users(mall_status);
CREATE INDEX IF NOT EXISTS idx_ak_users_mall_type ON public.ak_users(mall_type);
CREATE INDEX IF NOT EXISTS idx_ak_users_total_orders ON public.ak_users(total_orders DESC);
-- =====================================================================================
-- 5. 创建序列(如果不存在)
-- =====================================================================================
CREATE SEQUENCE IF NOT EXISTS public.ml_order_seq START 1;
-- =====================================================================================
-- 6. 创建或替换触发器函数
-- =====================================================================================
-- 自动更新 updated_at 字段的函数
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 确保每个用户只有一个默认地址的触发器函数
CREATE OR REPLACE FUNCTION public.ensure_single_default_address()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_default = TRUE THEN
UPDATE public.ml_user_addresses
SET is_default = FALSE
WHERE user_id = NEW.user_id AND id != NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 商品库存更新触发器函数
CREATE OR REPLACE FUNCTION public.update_product_stock()
RETURNS TRIGGER AS $$
BEGIN
-- 更新商品总库存
UPDATE public.ml_products
SET
total_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = COALESCE(NEW.product_id, OLD.product_id) AND status = 1
),
available_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = COALESCE(NEW.product_id, OLD.product_id) AND status = 1
)
WHERE id = COALESCE(NEW.product_id, OLD.product_id);
RETURN COALESCE(NEW, OLD);
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 = 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.ak_users
SET
total_orders = total_orders + 1,
total_spent = total_spent + NEW.total_amount
WHERE id = NEW.user_id;
-- 更新商品销量
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;
-- =====================================================================================
-- 7. 创建触发器
-- =====================================================================================
-- 删除可能存在的同名触发器,然后重新创建
DROP TRIGGER IF EXISTS trigger_ml_user_profiles_updated_at ON public.ml_user_profiles;
CREATE TRIGGER trigger_ml_user_profiles_updated_at
BEFORE UPDATE ON public.ml_user_profiles
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_user_addresses_updated_at ON public.ml_user_addresses;
CREATE TRIGGER trigger_ml_user_addresses_updated_at
BEFORE UPDATE ON public.ml_user_addresses
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_products_updated_at ON public.ml_products;
CREATE TRIGGER trigger_ml_products_updated_at
BEFORE UPDATE ON public.ml_products
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_product_skus_updated_at ON public.ml_product_skus;
CREATE TRIGGER trigger_ml_product_skus_updated_at
BEFORE UPDATE ON public.ml_product_skus
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_shops_updated_at ON public.ml_shops;
CREATE TRIGGER trigger_ml_shops_updated_at
BEFORE UPDATE ON public.ml_shops
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_orders_updated_at ON public.ml_orders;
CREATE TRIGGER trigger_ml_orders_updated_at
BEFORE UPDATE ON public.ml_orders
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_shopping_cart_updated_at ON public.ml_shopping_cart;
CREATE TRIGGER trigger_ml_shopping_cart_updated_at
BEFORE UPDATE ON public.ml_shopping_cart
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ml_single_default_address ON public.ml_user_addresses;
CREATE TRIGGER trigger_ml_single_default_address
BEFORE INSERT OR UPDATE ON public.ml_user_addresses
FOR EACH ROW EXECUTE FUNCTION public.ensure_single_default_address();
DROP TRIGGER IF EXISTS trigger_ml_update_product_stock ON public.ml_product_skus;
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();
DROP TRIGGER IF EXISTS trigger_ml_order_status_change ON public.ml_orders;
CREATE TRIGGER trigger_ml_order_status_change
BEFORE UPDATE ON public.ml_orders
FOR EACH ROW EXECUTE FUNCTION public.handle_order_status_change();
-- =====================================================================================
-- 8. 创建实用函数
-- =====================================================================================
-- 生成订单号的函数
CREATE OR REPLACE FUNCTION public.generate_order_no()
RETURNS TEXT AS $$
DECLARE
order_no TEXT;
BEGIN
order_no := 'ML' || TO_CHAR(NOW(), 'YYYYMMDD') || LPAD(NEXTVAL('ml_order_seq')::TEXT, 6, '0');
RETURN order_no;
END;
$$ LANGUAGE plpgsql;
-- 生成优惠券码的函数
CREATE OR REPLACE FUNCTION public.generate_coupon_code()
RETURNS TEXT AS $$
DECLARE
code TEXT;
chars TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
result TEXT := '';
i INTEGER;
BEGIN
FOR i IN 1..8 LOOP
result := result || substr(chars, (random() * length(chars))::integer + 1, 1);
END LOOP;
RETURN 'CP' || result;
END;
$$ LANGUAGE plpgsql;
-- 检查用户是否为认证商家
CREATE OR REPLACE FUNCTION public.is_verified_merchant(p_user_id UUID)
RETURNS BOOLEAN AS $$
DECLARE
result BOOLEAN := FALSE;
BEGIN
SELECT (user_type = 2 AND verification_status = 1) INTO result
FROM public.ml_user_profiles
WHERE user_id = p_user_id;
RETURN COALESCE(result, FALSE);
END;
$$ LANGUAGE plpgsql;
-- 计算购物车总金额
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 c.sku_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;
-- 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
) 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
FROM public.ml_products p
WHERE p.cid = p_cid AND p.status = 1;
END;
$$ LANGUAGE plpgsql;
-- =====================================================================================
-- 9. 创建视图
-- =====================================================================================
-- 商城用户完整信息视图
CREATE OR REPLACE VIEW public.ml_users_view AS
SELECT
u.id,
u.username,
u.email,
u.phone,
u.avatar_url,
u.gender,
u.birthday,
u.bio,
u.created_at as user_created_at,
u.updated_at as user_updated_at,
u.mall_status,
u.mall_type,
u.total_orders,
u.total_spent,
p.user_type,
p.status,
p.real_name,
p.credit_score,
p.verification_status,
CASE
WHEN p.user_type = 1 THEN '消费者'
WHEN p.user_type = 2 THEN '商家'
WHEN p.user_type = 3 THEN '配送员'
WHEN p.user_type = 4 THEN '客服'
WHEN p.user_type = 5 THEN '管理员'
ELSE '未知'
END as user_type_name
FROM public.ak_users u
LEFT JOIN public.ml_user_profiles p ON u.id = p.user_id;
-- 商品详情视图
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;
-- =====================================================================================
-- 10. 初始化基础数据
-- =====================================================================================
-- 插入默认分类(如果不存在)
INSERT INTO public.ml_categories (id, name, slug, level, path)
SELECT * FROM (VALUES
(uuid_generate_v4(), '数码电器', 'digital', 1, ARRAY['数码电器']),
(uuid_generate_v4(), '服装鞋帽', 'fashion', 1, ARRAY['服装鞋帽']),
(uuid_generate_v4(), '家居用品', 'home', 1, ARRAY['家居用品']),
(uuid_generate_v4(), '食品饮料', 'food', 1, ARRAY['食品饮料']),
(uuid_generate_v4(), '美妆护肤', 'beauty', 1, ARRAY['美妆护肤'])
) AS v(id, name, slug, level, path)
WHERE NOT EXISTS (SELECT 1 FROM public.ml_categories WHERE slug = v.slug);
-- 为现有 ak_users 用户创建默认商城档案(如果不存在)
INSERT INTO public.ml_user_profiles (user_id, user_type, status)
SELECT
id,
1, -- 默认为消费者
1 -- 默认状态正常
FROM public.ak_users
WHERE id NOT IN (SELECT user_id FROM public.ml_user_profiles WHERE user_id IS NOT NULL);
-- =====================================================================================
-- 11. 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '商城系统增量升级完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '✓ 扩展创建完成';
RAISE NOTICE '✓ 商城表结构创建/检查完成';
RAISE NOTICE '✓ ak_users 表字段添加完成';
RAISE NOTICE '✓ 索引创建完成';
RAISE NOTICE '✓ 触发器创建完成';
RAISE NOTICE '✓ 实用函数创建完成';
RAISE NOTICE '✓ 视图创建完成';
RAISE NOTICE '✓ 基础数据初始化完成';
RAISE NOTICE '=======================================================';
RAISE NOTICE '使用说明:';
RAISE NOTICE '1. 此脚本安全执行,不会覆盖现有数据';
RAISE NOTICE '2. 使用 IF NOT EXISTS 和 IF EXISTS 检查避免重复';
RAISE NOTICE '3. 为现有用户自动创建商城档案';
RAISE NOTICE '4. 所有新表前缀: ml_';
RAISE NOTICE '5. 复用表: ak_users';
RAISE NOTICE '=======================================================';
END $$;

View File

@@ -1,332 +0,0 @@
-- =====================================================================================
-- 商城系统数据库状态检查脚本
-- 分析现有数据库结构生成个性化ALTER建议
-- =====================================================================================
-- =====================================================================================
-- 1. 检查现有表结构
-- =====================================================================================
-- 检查 ak_users 表字段情况
DO $$
DECLARE
missing_fields TEXT[] := ARRAY[]::TEXT[];
existing_fields TEXT[] := ARRAY[]::TEXT[];
field_name TEXT;
field_names TEXT[] := ARRAY['mall_status', 'mall_type', 'last_login_ip', 'total_orders', 'total_spent', 'user_level', 'points', 'verified_status'];
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '检查 ak_users 表字段状态';
RAISE NOTICE '=======================================================';
FOREACH field_name IN ARRAY field_names LOOP
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = field_name) THEN
existing_fields := array_append(existing_fields, field_name);
RAISE NOTICE '✓ 字段已存在: %', field_name;
ELSE
missing_fields := array_append(missing_fields, field_name);
RAISE NOTICE '✗ 字段缺失: %', field_name;
END IF;
END LOOP;
RAISE NOTICE '-------------------------------------------------------';
RAISE NOTICE '已存在字段数量: %', array_length(existing_fields, 1);
RAISE NOTICE '缺失字段数量: %', array_length(missing_fields, 1);
IF array_length(missing_fields, 1) > 0 THEN
RAISE NOTICE '需要添加的字段: %', array_to_string(missing_fields, ', ');
ELSE
RAISE NOTICE 'ak_users 表所有商城字段均已存在';
END IF;
END $$;
-- 检查商城表存在情况
DO $$
DECLARE
table_name TEXT;
table_names TEXT[] := ARRAY['ml_user_profiles', 'ml_user_addresses', 'ml_categories', 'ml_brands', 'ml_products', 'ml_product_skus', 'ml_shops', 'ml_orders', 'ml_shopping_cart'];
existing_tables TEXT[] := ARRAY[]::TEXT[];
missing_tables TEXT[] := ARRAY[]::TEXT[];
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '检查商城核心表存在情况';
RAISE NOTICE '=======================================================';
FOREACH table_name IN ARRAY table_names LOOP
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = table_name) THEN
existing_tables := array_append(existing_tables, table_name);
RAISE NOTICE '✓ 表已存在: %', table_name;
ELSE
missing_tables := array_append(missing_tables, table_name);
RAISE NOTICE '✗ 表缺失: %', table_name;
END IF;
END LOOP;
RAISE NOTICE '-------------------------------------------------------';
RAISE NOTICE '已存在表数量: %', array_length(existing_tables, 1);
RAISE NOTICE '缺失表数量: %', array_length(missing_tables, 1);
IF array_length(missing_tables, 1) > 0 THEN
RAISE NOTICE '需要创建的表: %', array_to_string(missing_tables, ', ');
ELSE
RAISE NOTICE '所有商城核心表均已存在';
END IF;
END $$;
-- =====================================================================================
-- 2. 检查现有索引情况
-- =====================================================================================
-- 检查重要索引存在情况
DO $$
DECLARE
index_info RECORD;
missing_indexes TEXT[] := ARRAY[]::TEXT[];
existing_indexes TEXT[] := ARRAY[]::TEXT[];
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '检查重要索引存在情况';
RAISE NOTICE '=======================================================';
-- 定义重要索引列表
FOR index_info IN
SELECT * FROM (VALUES
('idx_ak_users_mall_status', 'ak_users', 'mall_status'),
('idx_ak_users_mall_type', 'ak_users', 'mall_type'),
('idx_ak_users_total_orders', 'ak_users', 'total_orders'),
('idx_ml_products_cid', 'ml_products', 'cid'),
('idx_ml_products_slug', 'ml_products', 'slug'),
('idx_ml_categories_cid', 'ml_categories', 'cid'),
('idx_ml_orders_cid', 'ml_orders', 'cid'),
('idx_ml_shops_cid', 'ml_shops', 'cid')
) AS t(index_name, table_name, column_name)
LOOP
-- 检查表是否存在
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = index_info.table_name) THEN
-- 检查索引是否存在
IF EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = index_info.index_name) THEN
existing_indexes := array_append(existing_indexes, index_info.index_name);
RAISE NOTICE '✓ 索引已存在: % (表: %)', index_info.index_name, index_info.table_name;
ELSE
missing_indexes := array_append(missing_indexes, index_info.index_name);
RAISE NOTICE '✗ 索引缺失: % (表: %)', index_info.index_name, index_info.table_name;
END IF;
ELSE
RAISE NOTICE '○ 表不存在,跳过索引检查: % (表: %)', index_info.index_name, index_info.table_name;
END IF;
END LOOP;
RAISE NOTICE '-------------------------------------------------------';
RAISE NOTICE '已存在索引数量: %', array_length(existing_indexes, 1);
RAISE NOTICE '缺失索引数量: %', array_length(missing_indexes, 1);
END $$;
-- =====================================================================================
-- 3. 检查扩展和函数
-- =====================================================================================
-- 检查必要的PostgreSQL扩展
DO $$
DECLARE
ext_name TEXT;
extensions TEXT[] := ARRAY['uuid-ossp', 'btree_gin'];
existing_ext TEXT[] := ARRAY[]::TEXT[];
missing_ext TEXT[] := ARRAY[]::TEXT[];
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '检查PostgreSQL扩展';
RAISE NOTICE '=======================================================';
FOREACH ext_name IN ARRAY extensions LOOP
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = ext_name) THEN
existing_ext := array_append(existing_ext, ext_name);
RAISE NOTICE '✓ 扩展已安装: %', ext_name;
ELSE
missing_ext := array_append(missing_ext, ext_name);
RAISE NOTICE '✗ 扩展缺失: %', ext_name;
END IF;
END LOOP;
IF array_length(missing_ext, 1) > 0 THEN
RAISE NOTICE '需要安装的扩展: %', array_to_string(missing_ext, ', ');
END IF;
END $$;
-- 检查商城相关函数
DO $$
DECLARE
func_name TEXT;
functions TEXT[] := ARRAY['generate_order_no', 'calculate_cart_total', 'update_user_mall_stats'];
existing_funcs TEXT[] := ARRAY[]::TEXT[];
missing_funcs TEXT[] := ARRAY[]::TEXT[];
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '检查商城相关函数';
RAISE NOTICE '=======================================================';
FOREACH func_name IN ARRAY functions LOOP
IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = func_name) THEN
existing_funcs := array_append(existing_funcs, func_name);
RAISE NOTICE '✓ 函数已存在: %', func_name;
ELSE
missing_funcs := array_append(missing_funcs, func_name);
RAISE NOTICE '✗ 函数缺失: %', func_name;
END IF;
END LOOP;
IF array_length(missing_funcs, 1) > 0 THEN
RAISE NOTICE '需要创建的函数: %', array_to_string(missing_funcs, ', ');
END IF;
END $$;
-- =====================================================================================
-- 4. 生成个性化建议
-- =====================================================================================
DO $$
DECLARE
ak_users_missing INTEGER := 0;
mall_tables_missing INTEGER := 0;
suggestion TEXT := '';
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '个性化升级建议';
RAISE NOTICE '=======================================================';
-- 统计ak_users缺失字段
SELECT COUNT(*) INTO ak_users_missing
FROM (VALUES ('mall_status'), ('mall_type'), ('total_orders'), ('total_spent')) AS t(field)
WHERE NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'ak_users' AND column_name = t.field
);
-- 统计商城表缺失情况
SELECT COUNT(*) INTO mall_tables_missing
FROM (VALUES ('ml_products'), ('ml_categories'), ('ml_orders'), ('ml_shops')) AS t(table_name)
WHERE NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_name = t.table_name
);
-- 生成建议
IF ak_users_missing > 0 AND mall_tables_missing > 0 THEN
suggestion := '建议使用 mall_alter_upgrade.sql完整升级脚本';
ELSIF ak_users_missing > 0 AND mall_tables_missing = 0 THEN
suggestion := '建议使用 mall_fields_only_upgrade.sql仅字段升级脚本';
ELSIF ak_users_missing = 0 AND mall_tables_missing > 0 THEN
suggestion := '建议使用 mall_migration.sql表结构创建脚本';
ELSE
suggestion := '数据库结构已完整,建议检查数据完整性和权限配置';
END IF;
RAISE NOTICE '根据您的数据库状态分析:';
RAISE NOTICE '• ak_users 表缺失字段数: %', ak_users_missing;
RAISE NOTICE '• 缺失商城核心表数: %', mall_tables_missing;
RAISE NOTICE '';
RAISE NOTICE '推荐执行方案: %', suggestion;
-- 详细建议
RAISE NOTICE '';
RAISE NOTICE '详细执行步骤:';
IF ak_users_missing > 0 THEN
RAISE NOTICE '1. 先执行字段升级脚本为ak_users表添加商城字段';
END IF;
IF mall_tables_missing > 0 THEN
RAISE NOTICE '2. 执行表结构创建脚本建立商城核心表';
END IF;
RAISE NOTICE '3. 执行SEO和安全策略脚本mall_seo_security.sql';
RAISE NOTICE '4. 根据需要执行模拟数据插入脚本进行测试';
END $$;
-- =====================================================================================
-- 5. 生成具体的ALTER语句可选
-- =====================================================================================
-- 生成ak_users表缺失字段的ALTER语句
DO $$
DECLARE
alter_statements TEXT := '';
field_name TEXT;
field_configs TEXT[] := ARRAY[
'mall_status INTEGER DEFAULT 1 CHECK (mall_status IN (1,2))',
'mall_type INTEGER DEFAULT 1 CHECK (mall_type IN (1,2,3))',
'total_orders INTEGER DEFAULT 0 CHECK (total_orders >= 0)',
'total_spent DECIMAL(12,2) DEFAULT 0.00 CHECK (total_spent >= 0)',
'user_level INTEGER DEFAULT 1 CHECK (user_level >= 1 AND user_level <= 10)',
'points INTEGER DEFAULT 0 CHECK (points >= 0)',
'verified_status INTEGER DEFAULT 0 CHECK (verified_status IN (0,1,2))'
];
field_names TEXT[] := ARRAY['mall_status', 'mall_type', 'total_orders', 'total_spent', 'user_level', 'points', 'verified_status'];
i INTEGER;
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '生成ak_users表ALTER语句';
RAISE NOTICE '=======================================================';
FOR i IN 1..array_length(field_names, 1) LOOP
field_name := field_names[i];
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = field_name) THEN
alter_statements := alter_statements || format('ALTER TABLE public.ak_users ADD COLUMN %s;' || chr(10), field_configs[i]);
RAISE NOTICE '需要执行: ALTER TABLE public.ak_users ADD COLUMN %;', field_configs[i];
END IF;
END LOOP;
IF alter_statements = '' THEN
RAISE NOTICE 'ak_users表无需添加字段';
ELSE
RAISE NOTICE '';
RAISE NOTICE '完整ALTER脚本';
RAISE NOTICE '%', alter_statements;
END IF;
END $$;
-- =====================================================================================
-- 6. 数据完整性检查
-- =====================================================================================
DO $$
DECLARE
users_count INTEGER;
profiles_count INTEGER;
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '数据完整性检查';
RAISE NOTICE '=======================================================';
-- 检查用户表数据
SELECT COUNT(*) INTO users_count FROM public.ak_users;
RAISE NOTICE 'ak_users 表用户数量: %', users_count;
-- 检查用户档案表(如果存在)
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_user_profiles') THEN
SELECT COUNT(*) INTO profiles_count FROM public.ml_user_profiles;
RAISE NOTICE 'ml_user_profiles 表档案数量: %', profiles_count;
IF users_count > profiles_count THEN
RAISE NOTICE '注意: 有 % 个用户缺少商城档案,建议执行档案补充脚本', users_count - profiles_count;
END IF;
ELSE
RAISE NOTICE 'ml_user_profiles 表不存在';
END IF;
END $$;
-- =====================================================================================
-- 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '数据库状态检查完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '请根据上述分析结果选择合适的升级脚本:';
RAISE NOTICE '';
RAISE NOTICE '• mall_alter_upgrade.sql - 完整升级(表+字段+索引+函数)';
RAISE NOTICE '• mall_fields_only_upgrade.sql - 仅字段升级(最小化修改)';
RAISE NOTICE '• mall_migration.sql - 完整建表(全新部署)';
RAISE NOTICE '• mall_seo_security.sql - SEO优化和安全策略';
RAISE NOTICE '';
RAISE NOTICE '建议在生产环境执行前先在测试环境验证!';
RAISE NOTICE '=======================================================';
END $$;

View File

@@ -1,734 +0,0 @@
-- =====================================================================================
-- 商城系统字段增量添加脚本 (仅字段和索引)
-- 适用于已有表结构,仅添加缺失字段和索引的场景
-- =====================================================================================
-- =====================================================================================
-- 1. 为现有 ak_users 表添加商城字段
-- =====================================================================================
DO $$
BEGIN
-- 商城状态字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_status') THEN
ALTER TABLE public.ak_users ADD COLUMN mall_status INTEGER DEFAULT 1;
ALTER TABLE public.ak_users ADD CONSTRAINT chk_ak_users_mall_status CHECK (mall_status IN (1,2));
RAISE NOTICE '✓ 添加字段: ak_users.mall_status';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.mall_status';
END IF;
-- 商城用户类型字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_type') THEN
ALTER TABLE public.ak_users ADD COLUMN mall_type INTEGER DEFAULT 1;
ALTER TABLE public.ak_users ADD CONSTRAINT chk_ak_users_mall_type CHECK (mall_type IN (1,2,3));
RAISE NOTICE '✓ 添加字段: ak_users.mall_type';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.mall_type';
END IF;
-- 最后登录IP字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'last_login_ip') THEN
ALTER TABLE public.ak_users ADD COLUMN last_login_ip INET;
RAISE NOTICE '✓ 添加字段: ak_users.last_login_ip';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.last_login_ip';
END IF;
-- 总订单数字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_orders') THEN
ALTER TABLE public.ak_users ADD COLUMN total_orders INTEGER DEFAULT 0 CHECK (total_orders >= 0);
RAISE NOTICE '✓ 添加字段: ak_users.total_orders';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.total_orders';
END IF;
-- 总消费金额字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_spent') THEN
ALTER TABLE public.ak_users ADD COLUMN total_spent DECIMAL(12,2) DEFAULT 0.00 CHECK (total_spent >= 0);
RAISE NOTICE '✓ 添加字段: ak_users.total_spent';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.total_spent';
END IF;
-- 用户等级字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'user_level') THEN
ALTER TABLE public.ak_users ADD COLUMN user_level INTEGER DEFAULT 1 CHECK (user_level >= 1 AND user_level <= 10);
RAISE NOTICE '✓ 添加字段: ak_users.user_level';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.user_level';
END IF;
-- 积分字段
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'points') THEN
ALTER TABLE public.ak_users ADD COLUMN points INTEGER DEFAULT 0 CHECK (points >= 0);
RAISE NOTICE '✓ 添加字段: ak_users.points';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.points';
END IF;
-- 实名认证状态
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'verified_status') THEN
ALTER TABLE public.ak_users ADD COLUMN verified_status INTEGER DEFAULT 0 CHECK (verified_status IN (0,1,2));
RAISE NOTICE '✓ 添加字段: ak_users.verified_status';
ELSE
RAISE NOTICE '○ 字段已存在: ak_users.verified_status';
END IF;
RAISE NOTICE '>> ak_users 表字段检查完成';
END $$;
-- =====================================================================================
-- 2. 为现有商城表添加CID字段SEO优化必需
-- =====================================================================================
-- 为主要商城表添加cid自增字段
DO $$
BEGIN
-- 为 ml_categories 表添加 cid 字段
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_categories') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'cid') THEN
-- 创建序列
CREATE SEQUENCE IF NOT EXISTS public.ml_categories_cid_seq;
-- 添加cid字段
ALTER TABLE public.ml_categories ADD COLUMN cid INTEGER UNIQUE DEFAULT nextval('public.ml_categories_cid_seq');
-- 设置序列所有者
ALTER SEQUENCE public.ml_categories_cid_seq OWNED BY public.ml_categories.cid;
-- 更新现有记录的cid值
UPDATE public.ml_categories SET cid = nextval('public.ml_categories_cid_seq') WHERE cid IS NULL;
RAISE NOTICE '✓ 添加字段: ml_categories.cid (自增SEO ID)';
ELSE
RAISE NOTICE '○ 字段已存在: ml_categories.cid';
END IF;
END IF;
-- 为 ml_brands 表添加 cid 字段
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_brands') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_brands' AND column_name = 'cid') THEN
CREATE SEQUENCE IF NOT EXISTS public.ml_brands_cid_seq;
ALTER TABLE public.ml_brands ADD COLUMN cid INTEGER UNIQUE DEFAULT nextval('public.ml_brands_cid_seq');
ALTER SEQUENCE public.ml_brands_cid_seq OWNED BY public.ml_brands.cid;
UPDATE public.ml_brands SET cid = nextval('public.ml_brands_cid_seq') WHERE cid IS NULL;
RAISE NOTICE '✓ 添加字段: ml_brands.cid (自增SEO ID)';
ELSE
RAISE NOTICE '○ 字段已存在: ml_brands.cid';
END IF;
END IF;
-- 为 ml_products 表添加 cid 字段
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_products') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'cid') THEN
CREATE SEQUENCE IF NOT EXISTS public.ml_products_cid_seq;
ALTER TABLE public.ml_products ADD COLUMN cid INTEGER UNIQUE DEFAULT nextval('public.ml_products_cid_seq');
ALTER SEQUENCE public.ml_products_cid_seq OWNED BY public.ml_products.cid;
UPDATE public.ml_products SET cid = nextval('public.ml_products_cid_seq') WHERE cid IS NULL;
RAISE NOTICE '✓ 添加字段: ml_products.cid (自增SEO ID)';
ELSE
RAISE NOTICE '○ 字段已存在: ml_products.cid';
END IF;
END IF;
-- 为 ml_shops 表添加 cid 字段
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_shops') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_shops' AND column_name = 'cid') THEN
CREATE SEQUENCE IF NOT EXISTS public.ml_shops_cid_seq;
ALTER TABLE public.ml_shops ADD COLUMN cid INTEGER UNIQUE DEFAULT nextval('public.ml_shops_cid_seq');
ALTER SEQUENCE public.ml_shops_cid_seq OWNED BY public.ml_shops.cid;
UPDATE public.ml_shops SET cid = nextval('public.ml_shops_cid_seq') WHERE cid IS NULL;
RAISE NOTICE '✓ 添加字段: ml_shops.cid (自增SEO ID)';
ELSE
RAISE NOTICE '○ 字段已存在: ml_shops.cid';
END IF;
END IF;
-- 为 ml_orders 表添加 cid 字段
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_orders') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_orders' AND column_name = 'cid') THEN
CREATE SEQUENCE IF NOT EXISTS public.ml_orders_cid_seq;
ALTER TABLE public.ml_orders ADD COLUMN cid INTEGER UNIQUE DEFAULT nextval('public.ml_orders_cid_seq');
ALTER SEQUENCE public.ml_orders_cid_seq OWNED BY public.ml_orders.cid;
UPDATE public.ml_orders SET cid = nextval('public.ml_orders_cid_seq') WHERE cid IS NULL;
RAISE NOTICE '✓ 添加字段: ml_orders.cid (自增SEO ID)';
ELSE
RAISE NOTICE '○ 字段已存在: ml_orders.cid';
END IF;
END IF;
RAISE NOTICE '>> CID 字段添加完成';
END $$;
-- =====================================================================================
-- 3. 为现有商城表添加其他字段(如果表存在的话)
-- =====================================================================================
-- 为 ml_products 表添加SEO和营销字段
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_products') THEN
-- SEO标题
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'seo_title') THEN
ALTER TABLE public.ml_products ADD COLUMN seo_title VARCHAR(200);
RAISE NOTICE '✓ 添加字段: ml_products.seo_title';
END IF;
-- SEO描述
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'seo_description') THEN
ALTER TABLE public.ml_products ADD COLUMN seo_description VARCHAR(500);
RAISE NOTICE '✓ 添加字段: ml_products.seo_description';
END IF;
-- SEO关键词
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'seo_keywords') THEN
ALTER TABLE public.ml_products ADD COLUMN seo_keywords TEXT[];
RAISE NOTICE '✓ 添加字段: ml_products.seo_keywords';
END IF;
-- URL slug
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'slug') THEN
ALTER TABLE public.ml_products ADD COLUMN slug VARCHAR(200) UNIQUE;
RAISE NOTICE '✓ 添加字段: ml_products.slug';
END IF;
-- 标签
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'tags') THEN
ALTER TABLE public.ml_products ADD COLUMN tags TEXT[];
RAISE NOTICE '✓ 添加字段: ml_products.tags';
END IF;
-- 是否特色商品
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'is_featured') THEN
ALTER TABLE public.ml_products ADD COLUMN is_featured BOOLEAN DEFAULT FALSE;
RAISE NOTICE '✓ 添加字段: ml_products.is_featured';
END IF;
-- 是否新品
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'is_new') THEN
ALTER TABLE public.ml_products ADD COLUMN is_new BOOLEAN DEFAULT FALSE;
RAISE NOTICE '✓ 添加字段: ml_products.is_new';
END IF;
-- 是否热销
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'is_hot') THEN
ALTER TABLE public.ml_products ADD COLUMN is_hot BOOLEAN DEFAULT FALSE;
RAISE NOTICE '✓ 添加字段: ml_products.is_hot';
END IF;
-- 浏览次数
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'view_count') THEN
ALTER TABLE public.ml_products ADD COLUMN view_count INTEGER DEFAULT 0;
RAISE NOTICE '✓ 添加字段: ml_products.view_count';
END IF;
-- 销售数量
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'sale_count') THEN
ALTER TABLE public.ml_products ADD COLUMN sale_count INTEGER DEFAULT 0;
RAISE NOTICE '✓ 添加字段: ml_products.sale_count';
END IF;
-- 收藏数量
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'favorite_count') THEN
ALTER TABLE public.ml_products ADD COLUMN favorite_count INTEGER DEFAULT 0;
RAISE NOTICE '✓ 添加字段: ml_products.favorite_count';
END IF;
RAISE NOTICE '>> ml_products 表字段检查完成';
ELSE
RAISE NOTICE '○ ml_products 表不存在,跳过字段添加';
END IF;
END $$;
-- 为 ml_categories 表添加SEO字段
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_categories') THEN
-- SEO标题
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'seo_title') THEN
ALTER TABLE public.ml_categories ADD COLUMN seo_title VARCHAR(200);
RAISE NOTICE '✓ 添加字段: ml_categories.seo_title';
END IF;
-- SEO描述
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'seo_description') THEN
ALTER TABLE public.ml_categories ADD COLUMN seo_description VARCHAR(500);
RAISE NOTICE '✓ 添加字段: ml_categories.seo_description';
END IF;
-- URL slug
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'slug') THEN
ALTER TABLE public.ml_categories ADD COLUMN slug VARCHAR(200) UNIQUE;
RAISE NOTICE '✓ 添加字段: ml_categories.slug';
END IF;
RAISE NOTICE '>> ml_categories 表字段检查完成';
ELSE
RAISE NOTICE '○ ml_categories 表不存在,跳过字段添加';
END IF;
END $$;
-- =====================================================================================
-- 4. 创建CID字段索引SEO优化必需
-- =====================================================================================
-- 为CID字段创建索引
DO $$
BEGIN
-- ml_categories cid 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'cid') THEN
CREATE INDEX IF NOT EXISTS idx_ml_categories_cid ON public.ml_categories(cid);
RAISE NOTICE '✓ 创建索引: idx_ml_categories_cid';
END IF;
-- ml_brands cid 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_brands' AND column_name = 'cid') THEN
CREATE INDEX IF NOT EXISTS idx_ml_brands_cid ON public.ml_brands(cid);
RAISE NOTICE '✓ 创建索引: idx_ml_brands_cid';
END IF;
-- ml_products cid 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'cid') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_cid ON public.ml_products(cid);
RAISE NOTICE '✓ 创建索引: idx_ml_products_cid';
END IF;
-- ml_shops cid 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_shops' AND column_name = 'cid') THEN
CREATE INDEX IF NOT EXISTS idx_ml_shops_cid ON public.ml_shops(cid);
RAISE NOTICE '✓ 创建索引: idx_ml_shops_cid';
END IF;
-- ml_orders cid 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_orders' AND column_name = 'cid') THEN
CREATE INDEX IF NOT EXISTS idx_ml_orders_cid ON public.ml_orders(cid);
RAISE NOTICE '✓ 创建索引: idx_ml_orders_cid';
END IF;
RAISE NOTICE '>> CID 索引创建完成';
END $$;
-- =====================================================================================
-- 5. 创建索引(仅在字段存在时创建)
-- =====================================================================================
-- ak_users 表索引
DO $$
BEGIN
-- 商城状态索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_status') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_mall_status ON public.ak_users(mall_status);
RAISE NOTICE '✓ 创建索引: idx_ak_users_mall_status';
END IF;
-- 商城类型索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_type') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_mall_type ON public.ak_users(mall_type);
RAISE NOTICE '✓ 创建索引: idx_ak_users_mall_type';
END IF;
-- 订单数量索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_orders') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_total_orders ON public.ak_users(total_orders DESC);
RAISE NOTICE '✓ 创建索引: idx_ak_users_total_orders';
END IF;
-- 消费金额索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'total_spent') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_total_spent ON public.ak_users(total_spent DESC);
RAISE NOTICE '✓ 创建索引: idx_ak_users_total_spent';
END IF;
-- 用户等级索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'user_level') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_level ON public.ak_users(user_level);
RAISE NOTICE '✓ 创建索引: idx_ak_users_level';
END IF;
-- 积分索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'points') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_points ON public.ak_users(points DESC);
RAISE NOTICE '✓ 创建索引: idx_ak_users_points';
END IF;
-- 认证状态索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'verified_status') THEN
CREATE INDEX IF NOT EXISTS idx_ak_users_verified ON public.ak_users(verified_status);
RAISE NOTICE '✓ 创建索引: idx_ak_users_verified';
END IF;
RAISE NOTICE '>> ak_users 表索引创建完成';
END $$;
-- ml_products 表索引
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_products') THEN
-- slug 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'slug') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_slug ON public.ml_products(slug);
RAISE NOTICE '✓ 创建索引: idx_ml_products_slug';
END IF;
-- 特色商品索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'is_featured') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_featured ON public.ml_products(is_featured, status);
RAISE NOTICE '✓ 创建索引: idx_ml_products_featured';
END IF;
-- 标签索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'tags') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_tags ON public.ml_products USING GIN(tags);
RAISE NOTICE '✓ 创建索引: idx_ml_products_tags (GIN)';
END IF;
-- 浏览量索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'view_count') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_view_count ON public.ml_products(view_count DESC);
RAISE NOTICE '✓ 创建索引: idx_ml_products_view_count';
END IF;
-- 销量索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'sale_count') THEN
CREATE INDEX IF NOT EXISTS idx_ml_products_sale_count ON public.ml_products(sale_count DESC);
RAISE NOTICE '✓ 创建索引: idx_ml_products_sale_count';
END IF;
RAISE NOTICE '>> ml_products 表索引创建完成';
ELSE
RAISE NOTICE '○ ml_products 表不存在,跳过索引创建';
END IF;
END $$;
-- ml_categories 表索引
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_categories') THEN
-- slug 索引
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'slug') THEN
CREATE INDEX IF NOT EXISTS idx_ml_categories_slug ON public.ml_categories(slug);
RAISE NOTICE '✓ 创建索引: idx_ml_categories_slug';
END IF;
RAISE NOTICE '>> ml_categories 表索引创建完成';
ELSE
RAISE NOTICE '○ ml_categories 表不存在,跳过索引创建';
END IF;
END $$;
-- =====================================================================================
-- 6. 创建或更新约束
-- =====================================================================================
DO $$
BEGIN
-- ak_users 表约束检查
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_status') THEN
-- 检查约束是否存在,不存在则添加
IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_name = 'chk_ak_users_mall_status') THEN
ALTER TABLE public.ak_users ADD CONSTRAINT chk_ak_users_mall_status CHECK (mall_status IN (1,2));
RAISE NOTICE '✓ 添加约束: chk_ak_users_mall_status';
END IF;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'mall_type') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_name = 'chk_ak_users_mall_type') THEN
ALTER TABLE public.ak_users ADD CONSTRAINT chk_ak_users_mall_type CHECK (mall_type IN (1,2,3));
RAISE NOTICE '✓ 添加约束: chk_ak_users_mall_type';
END IF;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_users' AND column_name = 'verified_status') THEN
IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_name = 'chk_ak_users_verified_status') THEN
ALTER TABLE public.ak_users ADD CONSTRAINT chk_ak_users_verified_status CHECK (verified_status IN (0,1,2));
RAISE NOTICE '✓ 添加约束: chk_ak_users_verified_status';
END IF;
END IF;
RAISE NOTICE '>> 约束检查完成';
END $$;
-- =====================================================================================
-- 7. 创建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
) 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
FROM public.ml_products p
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
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_products') THEN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_products' AND column_name = 'slug') THEN
UPDATE public.ml_products
SET slug = LOWER(REGEXP_REPLACE(TRIM(name), '[^a-zA-Z0-9\u4e00-\u9fa5]+', '-', 'g'))
WHERE slug IS NULL OR slug = '';
END IF;
END IF;
-- 更新分类 slug
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_categories') THEN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_categories' AND column_name = 'slug') THEN
UPDATE public.ml_categories
SET slug = LOWER(REGEXP_REPLACE(TRIM(name), '[^a-zA-Z0-9\u4e00-\u9fa5]+', '-', 'g'))
WHERE slug IS NULL OR slug = '';
END IF;
END IF;
RAISE NOTICE 'SEO slugs updated successfully';
END;
$$ LANGUAGE plpgsql;
-- =====================================================================================
-- 8. 创建基础函数(如果不存在)
-- =====================================================================================
-- 更新用户商城统计数据的函数
CREATE OR REPLACE FUNCTION public.update_user_mall_stats(p_user_id UUID)
RETURNS VOID AS $$
BEGIN
UPDATE public.ak_users
SET
total_orders = (
SELECT COUNT(*)
FROM public.ml_orders
WHERE user_id = p_user_id AND order_status = 4
),
total_spent = (
SELECT COALESCE(SUM(total_amount), 0)
FROM public.ml_orders
WHERE user_id = p_user_id AND order_status = 4
)
WHERE id = p_user_id;
END;
$$ LANGUAGE plpgsql;
-- 为用户计算等级的函数
CREATE OR REPLACE FUNCTION public.calculate_user_level(p_total_spent DECIMAL)
RETURNS INTEGER AS $$
BEGIN
CASE
WHEN p_total_spent >= 100000 THEN RETURN 10;
WHEN p_total_spent >= 50000 THEN RETURN 9;
WHEN p_total_spent >= 20000 THEN RETURN 8;
WHEN p_total_spent >= 10000 THEN RETURN 7;
WHEN p_total_spent >= 5000 THEN RETURN 6;
WHEN p_total_spent >= 2000 THEN RETURN 5;
WHEN p_total_spent >= 1000 THEN RETURN 4;
WHEN p_total_spent >= 500 THEN RETURN 3;
WHEN p_total_spent >= 100 THEN RETURN 2;
ELSE RETURN 1;
END CASE;
END;
$$ LANGUAGE plpgsql;
-- 批量更新用户等级的函数
CREATE OR REPLACE FUNCTION public.update_all_user_levels()
RETURNS INTEGER AS $$
DECLARE
affected_rows INTEGER := 0;
BEGIN
UPDATE public.ak_users
SET user_level = public.calculate_user_level(total_spent)
WHERE total_spent > 0;
GET DIAGNOSTICS affected_rows = ROW_COUNT;
RETURN affected_rows;
END;
$$ LANGUAGE plpgsql;
-- =====================================================================================
-- 9. 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '商城系统字段增量添加完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '执行内容:';
RAISE NOTICE '✓ ak_users 表增加商城相关字段';
RAISE NOTICE '✓ 商城核心表增加 cid 自增字段 (SEO优化)';
RAISE NOTICE '✓ 现有商城表增加SEO和营销字段';
RAISE NOTICE '✓ 创建相应的索引 (包括CID索引)';
RAISE NOTICE '✓ 添加约束检查';
RAISE NOTICE '✓ 创建SEO相关函数';
RAISE NOTICE '✓ 创建实用函数';
RAISE NOTICE '=======================================================';
RAISE NOTICE '新增字段说明:';
RAISE NOTICE '• ak_users.mall_status: 商城状态 (1:正常 2:禁用)';
RAISE NOTICE '• ak_users.mall_type: 用户类型 (1:消费者 2:商家 3:其他)';
RAISE NOTICE '• ak_users.total_orders: 总订单数';
RAISE NOTICE '• ak_users.total_spent: 总消费金额';
RAISE NOTICE '• ak_users.user_level: 用户等级 (1-10)';
RAISE NOTICE '• ak_users.points: 用户积分';
RAISE NOTICE '• ak_users.verified_status: 认证状态 (0:未认证 1:已认证 2:认证失败)';
RAISE NOTICE '-------------------------------------------------------';
RAISE NOTICE 'CID 字段说明 (SEO优化):';
RAISE NOTICE '• ml_categories.cid: 分类SEO友好ID';
RAISE NOTICE '• ml_brands.cid: 品牌SEO友好ID';
RAISE NOTICE '• ml_products.cid: 商品SEO友好ID';
RAISE NOTICE '• ml_shops.cid: 店铺SEO友好ID';
RAISE NOTICE '• ml_orders.cid: 订单SEO友好ID';
RAISE NOTICE '-------------------------------------------------------';
RAISE NOTICE 'SEO 函数说明:';
RAISE NOTICE '• get_product_by_cid(cid): 根据CID获取商品信息';
RAISE NOTICE '• get_category_by_cid(cid): 根据CID获取分类信息';
RAISE NOTICE '• get_brand_by_cid(cid): 根据CID获取品牌信息';
RAISE NOTICE '• get_shop_by_cid(cid): 根据CID获取店铺信息';
RAISE NOTICE '• generate_seo_url(type, cid, slug): 生成SEO友好URL';
RAISE NOTICE '• update_seo_slugs(): 批量更新slug字段';
RAISE NOTICE '=======================================================';
RAISE NOTICE '使用建议:';
RAISE NOTICE '1. 此脚本可安全重复执行';
RAISE NOTICE '2. 使用 IF NOT EXISTS 检查避免重复操作';
RAISE NOTICE '3. 建议在测试环境先执行验证';
RAISE NOTICE '4. 可根据实际需要注释掉不需要的字段';
RAISE NOTICE '5. 执行后可调用 update_seo_slugs() 初始化slug字段';
RAISE NOTICE '=======================================================';
RAISE NOTICE 'SEO URL 示例:';
RAISE NOTICE '• 商品页面: /product/123/iphone-15-pro';
RAISE NOTICE '• 分类页面: /category/45/digital-electronics';
RAISE NOTICE '• 品牌页面: /brand/12/apple';
RAISE NOTICE '• 店铺页面: /shop/88/official-store';
RAISE NOTICE '=======================================================';
END $$;

View File

@@ -1,868 +0,0 @@
-- =====================================================================================
-- 商城系统数据库迁移脚本 (PostgreSQL + Supabase)
-- 用途: 在现有数据库基础上添加商城相关表和功能
-- 说明: 复用 ak_users 表,新增 ml_ 前缀的商城表
-- 执行方式: 直接在数据库中执行此脚本
-- =====================================================================================
-- 检查必要的扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
CREATE EXTENSION IF NOT EXISTS "btree_gin";
-- =====================================================================================
-- 1. 创建商城用户扩展表
-- =====================================================================================
-- 商城用户档案表
CREATE TABLE IF NOT EXISTS public.ml_user_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID UNIQUE NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
user_type INTEGER DEFAULT 1 NOT NULL, -- 1:消费者 2:商家 3:配送员 4:客服 5:管理员
status INTEGER DEFAULT 1 NOT NULL, -- 1:正常 2:冻结 3:注销 4:待审核
real_name VARCHAR(100), -- 真实姓名
id_card VARCHAR(32), -- 身份证号
business_license VARCHAR(100), -- 营业执照号
credit_score INTEGER DEFAULT 100, -- 信用分数 0-1000
verification_status INTEGER DEFAULT 0, -- 认证状态 0:未认证 1:已认证 2:认证失败
verification_data JSONB DEFAULT '{}', -- 认证相关数据
preferences JSONB DEFAULT '{}', -- 用户偏好设置
emergency_contact VARCHAR(200), -- 紧急联系人
service_areas JSONB, -- 服务区域(配送员)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_user_type CHECK (user_type IN (1,2,3,4,5)),
CONSTRAINT chk_ml_user_status CHECK (status IN (1,2,3,4)),
CONSTRAINT chk_ml_verification_status CHECK (verification_status IN (0,1,2)),
CONSTRAINT chk_ml_credit_score CHECK (credit_score >= 0 AND credit_score <= 1000)
);
COMMENT ON TABLE public.ml_user_profiles IS '商城用户扩展信息表';
-- 用户地址表
CREATE TABLE IF NOT EXISTS public.ml_user_addresses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
receiver_name VARCHAR(100) NOT NULL,
receiver_phone VARCHAR(32) NOT NULL,
province VARCHAR(100) NOT NULL,
city VARCHAR(100) NOT NULL,
district VARCHAR(100) NOT NULL,
street VARCHAR(200),
address_detail TEXT NOT NULL,
postal_code VARCHAR(16),
is_default BOOLEAN DEFAULT FALSE,
label VARCHAR(50), -- home/office/school/other
latitude DECIMAL(10,7),
longitude DECIMAL(10,7),
delivery_instructions TEXT,
business_hours VARCHAR(100),
status INTEGER DEFAULT 1, -- 1:正常 2:禁用
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_address_status CHECK (status IN (1,2))
);
COMMENT ON TABLE public.ml_user_addresses IS '用户地址表';
-- =====================================================================================
-- 2. 创建商品相关表
-- =====================================================================================
-- 商品分类表
CREATE TABLE IF NOT EXISTS public.ml_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
parent_id UUID REFERENCES public.ml_categories(id),
name VARCHAR(200) NOT NULL,
slug VARCHAR(200) UNIQUE,
description TEXT,
icon_url TEXT,
banner_url TEXT,
sort_order INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
path TEXT[], -- 分类路径
is_active BOOLEAN DEFAULT TRUE,
seo_title VARCHAR(200),
seo_description VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_categories IS '商品分类表';
-- 品牌表
CREATE TABLE IF NOT EXISTS public.ml_brands (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
name VARCHAR(200) NOT NULL,
logo_url TEXT,
description TEXT,
website VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_brands IS '品牌表';
-- 商品表
CREATE TABLE IF NOT EXISTS public.ml_products (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
category_id UUID NOT NULL REFERENCES public.ml_categories(id),
brand_id UUID REFERENCES public.ml_brands(id),
product_code VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(500) NOT NULL,
subtitle VARCHAR(1000),
description TEXT,
main_image_url TEXT,
image_urls JSONB DEFAULT '[]',
video_urls JSONB DEFAULT '[]',
-- 价格信息
base_price DECIMAL(12,2) NOT NULL CHECK (base_price >= 0),
market_price DECIMAL(12,2),
cost_price DECIMAL(12,2),
-- 库存信息
total_stock INTEGER DEFAULT 0 CHECK (total_stock >= 0),
available_stock INTEGER DEFAULT 0 CHECK (available_stock >= 0),
min_order_qty INTEGER DEFAULT 1 CHECK (min_order_qty > 0),
max_order_qty INTEGER,
-- 基础属性
weight DECIMAL(10,3),
dimensions JSONB, -- {length, width, height}
-- 状态
status INTEGER DEFAULT 1, -- 1:上架 2:下架 3:草稿 4:删除
is_featured BOOLEAN DEFAULT FALSE,
is_new BOOLEAN DEFAULT FALSE,
is_hot BOOLEAN DEFAULT FALSE,
-- 统计
view_count INTEGER DEFAULT 0,
sale_count INTEGER DEFAULT 0,
favorite_count INTEGER DEFAULT 0,
rating_avg DECIMAL(3,2) DEFAULT 0.00 CHECK (rating_avg >= 0 AND rating_avg <= 5),
rating_count INTEGER DEFAULT 0,
-- SEO
seo_title VARCHAR(200),
seo_description VARCHAR(500),
seo_keywords TEXT[],
slug VARCHAR(200) UNIQUE,
-- 其他
tags TEXT[],
attributes JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
published_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT chk_ml_product_status CHECK (status IN (1,2,3,4))
);
COMMENT ON TABLE public.ml_products IS '商品表';
-- 商品SKU表
CREATE TABLE IF NOT EXISTS public.ml_product_skus (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
sku_code VARCHAR(100) UNIQUE NOT NULL,
specifications JSONB DEFAULT '{}', -- 规格组合
price DECIMAL(12,2) NOT NULL CHECK (price >= 0),
market_price DECIMAL(12,2),
cost_price DECIMAL(12,2),
stock INTEGER DEFAULT 0 CHECK (stock >= 0),
warning_stock INTEGER DEFAULT 10, -- 库存预警
image_url TEXT,
weight DECIMAL(10,3),
status INTEGER DEFAULT 1, -- 1:正常 2:禁用
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_sku_status CHECK (status IN (1,2))
);
COMMENT ON TABLE public.ml_product_skus IS '商品SKU表';
-- 商品规格表
CREATE TABLE IF NOT EXISTS public.ml_product_specs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
spec_name VARCHAR(100) NOT NULL, -- 规格名称:颜色、尺寸等
spec_values JSONB NOT NULL DEFAULT '[]', -- 规格值数组
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_product_specs IS '商品规格表';
-- =====================================================================================
-- 3. 创建店铺相关表
-- =====================================================================================
-- 店铺信息表
CREATE TABLE IF NOT EXISTS public.ml_shops (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
merchant_id UUID UNIQUE NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
shop_name VARCHAR(200) NOT NULL,
shop_logo TEXT,
shop_banner TEXT,
description TEXT,
business_license VARCHAR(100),
contact_name VARCHAR(100),
contact_phone VARCHAR(32),
contact_email VARCHAR(200),
address JSONB, -- 店铺地址信息
business_hours JSONB, -- 营业时间
-- 状态
status INTEGER DEFAULT 1, -- 1:正常 2:暂停 3:关闭
-- 统计
product_count INTEGER DEFAULT 0,
order_count INTEGER DEFAULT 0,
rating_avg DECIMAL(3,2) DEFAULT 0.00,
rating_count INTEGER DEFAULT 0,
-- 认证信息
verified_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_shop_status CHECK (status IN (1,2,3))
);
COMMENT ON TABLE public.ml_shops IS '店铺信息表';
-- =====================================================================================
-- 4. 创建订单相关表
-- =====================================================================================
-- 订单表
CREATE TABLE IF NOT EXISTS public.ml_orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
order_no VARCHAR(50) UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES public.ak_users(id),
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
-- 金额信息
product_amount DECIMAL(12,2) NOT NULL DEFAULT 0, -- 商品金额
discount_amount DECIMAL(12,2) DEFAULT 0, -- 优惠金额
shipping_fee DECIMAL(12,2) DEFAULT 0, -- 运费
total_amount DECIMAL(12,2) NOT NULL, -- 总金额
paid_amount DECIMAL(12,2) DEFAULT 0, -- 已付金额
-- 地址信息
shipping_address JSONB NOT NULL, -- 收货地址
-- 状态信息
order_status INTEGER DEFAULT 1, -- 1:待付款 2:待发货 3:待收货 4:已完成 5:已取消 6:退款中 7:已退款
payment_status INTEGER DEFAULT 1, -- 1:未付款 2:已付款 3:部分退款 4:全额退款
shipping_status INTEGER DEFAULT 1, -- 1:未发货 2:已发货 3:运输中 4:已送达
-- 时间信息
paid_at TIMESTAMP WITH TIME ZONE,
shipped_at TIMESTAMP WITH TIME ZONE,
delivered_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
-- 其他信息
remark TEXT, -- 买家备注
merchant_memo TEXT, -- 商家备注
cancel_reason TEXT, -- 取消原因
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_order_status CHECK (order_status IN (1,2,3,4,5,6,7)),
CONSTRAINT chk_ml_payment_status CHECK (payment_status IN (1,2,3,4)),
CONSTRAINT chk_ml_shipping_status CHECK (shipping_status IN (1,2,3,4))
);
COMMENT ON TABLE public.ml_orders IS '订单表';
-- 订单商品表
CREATE TABLE IF NOT EXISTS public.ml_order_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_id UUID NOT NULL REFERENCES public.ml_orders(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES public.ml_products(id),
sku_id UUID REFERENCES public.ml_product_skus(id),
product_name VARCHAR(500) NOT NULL,
sku_name VARCHAR(500),
specifications JSONB DEFAULT '{}',
image_url TEXT,
price DECIMAL(12,2) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
total_amount DECIMAL(12,2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_order_items IS '订单商品表';
-- =====================================================================================
-- 5. 创建购物车和营销相关表
-- =====================================================================================
-- 购物车表
CREATE TABLE IF NOT EXISTS public.ml_shopping_cart (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
sku_id UUID REFERENCES public.ml_product_skus(id) ON DELETE CASCADE,
quantity INTEGER NOT NULL CHECK (quantity > 0),
selected BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, product_id, sku_id)
);
COMMENT ON TABLE public.ml_shopping_cart IS '购物车表';
-- 优惠券模板表
CREATE TABLE IF NOT EXISTS public.ml_coupon_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cid SERIAL UNIQUE NOT NULL, -- SEO友好的自增ID
merchant_id UUID REFERENCES public.ak_users(id), -- NULL表示平台券
name VARCHAR(200) NOT NULL,
description TEXT,
coupon_type INTEGER NOT NULL, -- 1:满减券 2:折扣券 3:免运费券
discount_type INTEGER NOT NULL, -- 1:固定金额 2:百分比
discount_value DECIMAL(12,2) NOT NULL, -- 优惠值
min_order_amount DECIMAL(12,2) DEFAULT 0, -- 最低订单金额
max_discount_amount DECIMAL(12,2), -- 最大优惠金额
total_quantity INTEGER, -- 总发放数量
per_user_limit INTEGER DEFAULT 1, -- 每用户限领数量
usage_limit INTEGER DEFAULT 1, -- 每张券使用次数限制
-- 适用范围
applicable_products JSONB DEFAULT '[]', -- 适用商品ID数组
applicable_categories JSONB DEFAULT '[]', -- 适用分类ID数组
-- 时间限制
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
status INTEGER DEFAULT 1, -- 1:正常 2:暂停 3:已结束
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_coupon_type CHECK (coupon_type IN (1,2,3)),
CONSTRAINT chk_ml_discount_type CHECK (discount_type IN (1,2)),
CONSTRAINT chk_ml_coupon_status CHECK (status IN (1,2,3))
);
COMMENT ON TABLE public.ml_coupon_templates IS '优惠券模板表';
-- 用户优惠券表
CREATE TABLE IF NOT EXISTS public.ml_user_coupons (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
template_id UUID NOT NULL REFERENCES public.ml_coupon_templates(id),
coupon_code VARCHAR(50) UNIQUE NOT NULL,
status INTEGER DEFAULT 1, -- 1:未使用 2:已使用 3:已过期
used_at TIMESTAMP WITH TIME ZONE,
order_id UUID REFERENCES public.ml_orders(id),
received_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expire_at TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT chk_ml_user_coupon_status CHECK (status IN (1,2,3))
);
COMMENT ON TABLE public.ml_user_coupons IS '用户优惠券表';
-- =====================================================================================
-- 6. 创建配送和评价相关表
-- =====================================================================================
-- 配送员信息表
CREATE TABLE IF NOT EXISTS public.ml_delivery_drivers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID UNIQUE NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
real_name VARCHAR(100) NOT NULL,
id_card VARCHAR(32) NOT NULL,
driver_license VARCHAR(50),
vehicle_type INTEGER, -- 1:电动车 2:摩托车 3:汽车
vehicle_number VARCHAR(20),
service_areas JSONB DEFAULT '[]', -- 服务区域
work_status INTEGER DEFAULT 1, -- 1:在线 2:忙碌 3:离线
current_lat DECIMAL(10,7),
current_lng DECIMAL(10,7),
rating_avg DECIMAL(3,2) DEFAULT 0.00,
rating_count INTEGER DEFAULT 0,
order_count INTEGER DEFAULT 0,
status INTEGER DEFAULT 1, -- 1:正常 2:暂停 3:离职
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_driver_vehicle_type CHECK (vehicle_type IN (1,2,3)),
CONSTRAINT chk_ml_driver_work_status CHECK (work_status IN (1,2,3)),
CONSTRAINT chk_ml_driver_status CHECK (status IN (1,2,3))
);
COMMENT ON TABLE public.ml_delivery_drivers IS '配送员信息表';
-- 配送任务表
CREATE TABLE IF NOT EXISTS public.ml_delivery_tasks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_id UUID UNIQUE NOT NULL REFERENCES public.ml_orders(id),
driver_id UUID REFERENCES public.ml_delivery_drivers(id),
pickup_address JSONB NOT NULL, -- 取货地址
delivery_address JSONB NOT NULL, -- 配送地址
distance DECIMAL(8,2), -- 配送距离(km)
estimated_time INTEGER, -- 预计配送时间(分钟)
delivery_fee DECIMAL(10,2) NOT NULL DEFAULT 0,
status INTEGER DEFAULT 1, -- 1:待接单 2:已接单 3:取货中 4:配送中 5:已送达 6:配送失败
-- 时间记录
assigned_at TIMESTAMP WITH TIME ZONE,
picked_at TIMESTAMP WITH TIME ZONE,
delivered_at TIMESTAMP WITH TIME ZONE,
-- 其他信息
delivery_code VARCHAR(10), -- 取货码
remark TEXT,
failure_reason TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_delivery_status CHECK (status IN (1,2,3,4,5,6))
);
COMMENT ON TABLE public.ml_delivery_tasks IS '配送任务表';
-- 商品评价表
CREATE TABLE IF NOT EXISTS public.ml_product_reviews (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
order_id UUID NOT NULL REFERENCES public.ml_orders(id),
order_item_id UUID NOT NULL REFERENCES public.ml_order_items(id),
user_id UUID NOT NULL REFERENCES public.ak_users(id),
product_id UUID NOT NULL REFERENCES public.ml_products(id),
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
content TEXT,
images JSONB DEFAULT '[]', -- 评价图片
is_anonymous BOOLEAN DEFAULT FALSE,
-- 商家回复
merchant_reply TEXT,
merchant_replied_at TIMESTAMP WITH TIME ZONE,
status INTEGER DEFAULT 1, -- 1:正常 2:已删除 3:已隐藏
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_ml_review_status CHECK (status IN (1,2,3))
);
COMMENT ON TABLE public.ml_product_reviews IS '商品评价表';
-- =====================================================================================
-- 7. 创建用户行为和系统配置表
-- =====================================================================================
-- 用户收藏表
CREATE TABLE IF NOT EXISTS public.ml_user_favorites (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
target_type INTEGER NOT NULL, -- 1:商品 2:店铺
target_id UUID NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, target_type, target_id),
CONSTRAINT chk_ml_favorite_type CHECK (target_type IN (1,2))
);
COMMENT ON TABLE public.ml_user_favorites IS '用户收藏表';
-- 用户浏览历史表
CREATE TABLE IF NOT EXISTS public.ml_browse_history (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES public.ml_products(id) ON DELETE CASCADE,
browse_duration INTEGER DEFAULT 0, -- 浏览时长(秒)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, product_id)
);
COMMENT ON TABLE public.ml_browse_history IS '用户浏览历史表';
-- 搜索记录表
CREATE TABLE IF NOT EXISTS public.ml_search_history (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.ak_users(id) ON DELETE CASCADE,
keyword VARCHAR(200) NOT NULL,
result_count INTEGER DEFAULT 0,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_search_history IS '搜索记录表';
-- 系统配置表
CREATE TABLE IF NOT EXISTS public.ml_system_configs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
config_key VARCHAR(100) UNIQUE NOT NULL,
config_value JSONB,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_system_configs IS '系统配置表';
-- 地区表
CREATE TABLE IF NOT EXISTS public.ml_regions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
parent_id UUID REFERENCES public.ml_regions(id),
name VARCHAR(100) NOT NULL,
code VARCHAR(20),
level INTEGER NOT NULL, -- 1:省份 2:城市 3:区县 4:街道
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_regions IS '地区表';
-- =====================================================================================
-- 8. 创建索引
-- =====================================================================================
-- 用户扩展表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_user_id ON public.ml_user_profiles(user_id);
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_type ON public.ml_user_profiles(user_type);
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_status ON public.ml_user_profiles(status);
-- 分类表索引
CREATE INDEX IF NOT EXISTS idx_ml_categories_cid ON public.ml_categories(cid);
CREATE INDEX IF NOT EXISTS idx_ml_categories_parent ON public.ml_categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_ml_categories_slug ON public.ml_categories(slug);
CREATE INDEX IF NOT EXISTS idx_ml_categories_level ON public.ml_categories(level, sort_order);
-- 品牌表索引
CREATE INDEX IF NOT EXISTS idx_ml_brands_cid ON public.ml_brands(cid);
CREATE INDEX IF NOT EXISTS idx_ml_brands_name ON public.ml_brands(name);
-- 地址表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_addresses_user_id ON public.ml_user_addresses(user_id);
CREATE INDEX IF NOT EXISTS idx_ml_user_addresses_default ON public.ml_user_addresses(user_id, is_default);
CREATE INDEX IF NOT EXISTS idx_ml_user_addresses_location ON public.ml_user_addresses(city, district);
-- 商品表索引
CREATE INDEX IF NOT EXISTS idx_ml_products_cid ON public.ml_products(cid);
CREATE INDEX IF NOT EXISTS idx_ml_products_merchant ON public.ml_products(merchant_id, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_category ON public.ml_products(category_id, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_status ON public.ml_products(status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_products_featured ON public.ml_products(is_featured, status);
CREATE INDEX IF NOT EXISTS idx_ml_products_price ON public.ml_products(base_price);
CREATE INDEX IF NOT EXISTS idx_ml_products_rating ON public.ml_products(rating_avg DESC, rating_count DESC);
CREATE INDEX IF NOT EXISTS idx_ml_products_sale_count ON public.ml_products(sale_count DESC);
CREATE INDEX IF NOT EXISTS idx_ml_products_tags ON public.ml_products USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_ml_products_slug ON public.ml_products(slug);
-- 店铺表索引
CREATE INDEX IF NOT EXISTS idx_ml_shops_cid ON public.ml_shops(cid);
CREATE INDEX IF NOT EXISTS idx_ml_shops_merchant ON public.ml_shops(merchant_id);
-- SKU表索引
CREATE INDEX IF NOT EXISTS idx_ml_product_skus_product ON public.ml_product_skus(product_id);
CREATE INDEX IF NOT EXISTS idx_ml_product_skus_code ON public.ml_product_skus(sku_code);
-- 订单表索引
CREATE INDEX IF NOT EXISTS idx_ml_orders_cid ON public.ml_orders(cid);
CREATE INDEX IF NOT EXISTS idx_ml_orders_user ON public.ml_orders(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_merchant ON public.ml_orders(merchant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_status ON public.ml_orders(order_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_orders_no ON public.ml_orders(order_no);
-- 订单商品表索引
CREATE INDEX IF NOT EXISTS idx_ml_order_items_order ON public.ml_order_items(order_id);
CREATE INDEX IF NOT EXISTS idx_ml_order_items_product ON public.ml_order_items(product_id);
-- 购物车表索引
CREATE INDEX IF NOT EXISTS idx_ml_shopping_cart_user ON public.ml_shopping_cart(user_id);
-- 优惠券模板表索引
CREATE INDEX IF NOT EXISTS idx_ml_coupon_templates_cid ON public.ml_coupon_templates(cid);
CREATE INDEX IF NOT EXISTS idx_ml_coupon_templates_merchant ON public.ml_coupon_templates(merchant_id);
-- 优惠券表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_coupons_user ON public.ml_user_coupons(user_id, status);
CREATE INDEX IF NOT EXISTS idx_ml_user_coupons_code ON public.ml_user_coupons(coupon_code);
-- 收藏表索引
CREATE INDEX IF NOT EXISTS idx_ml_user_favorites_user ON public.ml_user_favorites(user_id, target_type);
CREATE INDEX IF NOT EXISTS idx_ml_user_favorites_target ON public.ml_user_favorites(target_type, target_id);
-- 浏览历史索引
CREATE INDEX IF NOT EXISTS idx_ml_browse_history_user ON public.ml_browse_history(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ml_browse_history_product ON public.ml_browse_history(product_id);
-- =====================================================================================
-- 9. 创建触发器函数
-- =====================================================================================
-- 自动更新 updated_at 字段的函数
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器 (使用 DO 块避免重复创建错误)
DO $$
BEGIN
-- 用户档案更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_user_profiles_updated_at') THEN
CREATE TRIGGER trigger_ml_user_profiles_updated_at
BEFORE UPDATE ON public.ml_user_profiles
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- 用户地址更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_user_addresses_updated_at') THEN
CREATE TRIGGER trigger_ml_user_addresses_updated_at
BEFORE UPDATE ON public.ml_user_addresses
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- 商品更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_products_updated_at') THEN
CREATE TRIGGER trigger_ml_products_updated_at
BEFORE UPDATE ON public.ml_products
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- SKU更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_product_skus_updated_at') THEN
CREATE TRIGGER trigger_ml_product_skus_updated_at
BEFORE UPDATE ON public.ml_product_skus
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- 店铺更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_shops_updated_at') THEN
CREATE TRIGGER trigger_ml_shops_updated_at
BEFORE UPDATE ON public.ml_shops
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- 订单更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_orders_updated_at') THEN
CREATE TRIGGER trigger_ml_orders_updated_at
BEFORE UPDATE ON public.ml_orders
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
-- 购物车更新触发器
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_shopping_cart_updated_at') THEN
CREATE TRIGGER trigger_ml_shopping_cart_updated_at
BEFORE UPDATE ON public.ml_shopping_cart
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
END IF;
END $$;
-- 确保每个用户只有一个默认地址的触发器
CREATE OR REPLACE FUNCTION public.ensure_single_default_address()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_default = TRUE THEN
UPDATE public.ml_user_addresses
SET is_default = FALSE
WHERE user_id = NEW.user_id AND id != NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_single_default_address') THEN
CREATE TRIGGER trigger_ml_single_default_address
BEFORE INSERT OR UPDATE ON public.ml_user_addresses
FOR EACH ROW EXECUTE FUNCTION public.ensure_single_default_address();
END IF;
END $$;
-- =====================================================================================
-- 10. 创建实用函数
-- =====================================================================================
-- 创建订单序列
CREATE SEQUENCE IF NOT EXISTS public.ml_order_seq START 1;
-- 生成订单号的函数
CREATE OR REPLACE FUNCTION public.generate_order_no()
RETURNS TEXT AS $$
DECLARE
order_no TEXT;
BEGIN
order_no := 'ML' || TO_CHAR(NOW(), 'YYYYMMDD') || LPAD(NEXTVAL('ml_order_seq')::TEXT, 6, '0');
RETURN order_no;
END;
$$ LANGUAGE plpgsql;
-- 生成优惠券码的函数
CREATE OR REPLACE FUNCTION public.generate_coupon_code()
RETURNS TEXT AS $$
DECLARE
code TEXT;
chars TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
result TEXT := '';
i INTEGER;
BEGIN
FOR i IN 1..8 LOOP
result := result || substr(chars, (random() * length(chars))::integer + 1, 1);
END LOOP;
RETURN 'CP' || result;
END;
$$ LANGUAGE plpgsql;
-- 获取用户默认地址
CREATE OR REPLACE FUNCTION public.get_user_default_address(p_user_id UUID)
RETURNS TABLE (
id UUID,
receiver_name VARCHAR,
receiver_phone VARCHAR,
full_address TEXT,
latitude DECIMAL,
longitude DECIMAL
) AS $$
BEGIN
RETURN QUERY
SELECT
a.id,
a.receiver_name,
a.receiver_phone,
(a.province || ' ' || a.city || ' ' || a.district || ' ' || a.address_detail) as full_address,
a.latitude,
a.longitude
FROM public.ml_user_addresses a
WHERE a.user_id = p_user_id AND a.is_default = TRUE AND a.status = 1
LIMIT 1;
END;
$$ LANGUAGE plpgsql;
-- 检查用户是否为认证商家
CREATE OR REPLACE FUNCTION public.is_verified_merchant(p_user_id UUID)
RETURNS BOOLEAN AS $$
DECLARE
result BOOLEAN := FALSE;
BEGIN
SELECT (user_type = 2 AND verification_status = 1) INTO result
FROM public.ml_user_profiles
WHERE user_id = p_user_id;
RETURN COALESCE(result, FALSE);
END;
$$ LANGUAGE plpgsql;
-- =====================================================================================
-- 11. 创建基础视图
-- =====================================================================================
-- 商城用户完整信息视图
CREATE OR REPLACE VIEW public.ml_users_view AS
SELECT
u.id,
u.username,
u.email,
u.phone,
u.avatar_url,
u.gender,
u.birthday,
u.bio,
u.created_at as user_created_at,
u.updated_at as user_updated_at,
p.user_type,
p.status,
p.real_name,
p.credit_score,
p.verification_status,
p.created_at as profile_created_at,
p.updated_at as profile_updated_at,
CASE
WHEN p.user_type = 1 THEN '消费者'
WHEN p.user_type = 2 THEN '商家'
WHEN p.user_type = 3 THEN '配送员'
WHEN p.user_type = 4 THEN '客服'
WHEN p.user_type = 5 THEN '管理员'
ELSE '未知'
END as user_type_name
FROM public.ak_users u
LEFT JOIN public.ml_user_profiles p ON u.id = p.user_id;
COMMENT ON VIEW public.ml_users_view IS '商城用户完整信息视图';
-- =====================================================================================
-- 12. 插入初始配置数据
-- =====================================================================================
-- 插入系统配置
INSERT INTO public.ml_system_configs (config_key, config_value, description) VALUES
('shipping_fee', '{"default": 10, "free_threshold": 88}', '配送费配置'),
('platform_commission', '{"rate": 0.05}', '平台佣金配置'),
('coupon_settings', '{"max_per_user": 10}', '优惠券设置'),
('order_auto_confirm_days', '7', '订单自动确认天数')
ON CONFLICT (config_key) DO NOTHING;
-- 插入默认分类
INSERT INTO public.ml_categories (id, name, slug, level, path) VALUES
(uuid_generate_v4(), '数码电器', 'digital', 1, ARRAY['数码电器']),
(uuid_generate_v4(), '服装鞋帽', 'fashion', 1, ARRAY['服装鞋帽']),
(uuid_generate_v4(), '家居用品', 'home', 1, ARRAY['家居用品']),
(uuid_generate_v4(), '食品饮料', 'food', 1, ARRAY['食品饮料']),
(uuid_generate_v4(), '美妆护肤', 'beauty', 1, ARRAY['美妆护肤'])
ON CONFLICT (slug) DO NOTHING;
-- 为现有 ak_users 用户创建默认商城档案 (如果不存在)
INSERT INTO public.ml_user_profiles (user_id, user_type, status)
SELECT
id,
1, -- 默认为消费者
1 -- 默认状态正常
FROM public.ak_users
WHERE id NOT IN (SELECT user_id FROM public.ml_user_profiles WHERE user_id IS NOT NULL);
-- =====================================================================================
-- 13. 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '商城数据库迁移完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '已创建表数量: 17 张商城表';
RAISE NOTICE '已创建索引: 30+ 个索引';
RAISE NOTICE '已创建触发器: 8 个触发器';
RAISE NOTICE '已创建函数: 6 个函数';
RAISE NOTICE '已创建视图: 1 个视图';
RAISE NOTICE '已插入基础配置和分类数据';
RAISE NOTICE '已为现有用户创建默认商城档案';
RAISE NOTICE '=======================================================';
RAISE NOTICE '表名前缀: ml_';
RAISE NOTICE '复用表: ak_users';
RAISE NOTICE '兼容: Supabase';
RAISE NOTICE '=======================================================';
END $$;

View File

@@ -1,666 +0,0 @@
-- =====================================================================================
-- 商城系统 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 $$;

File diff suppressed because it is too large Load Diff

View File

@@ -1,452 +0,0 @@
-- ===================================================================
-- 电商商城商品管理数据库设计
-- 基于PostgreSQL兼容现有ak_contents资讯系统
-- ===================================================================
-- ===================================================================
-- 1. 商品核心表
-- ===================================================================
-- 商品基础信息表
CREATE TABLE IF NOT EXISTS public.mall_products (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_code VARCHAR(50) UNIQUE NOT NULL, -- 商品编码
name VARCHAR(500) NOT NULL, -- 商品名称
subtitle VARCHAR(1000), -- 副标题/卖点
description TEXT, -- 商品描述
-- 商家信息
merchant_id UUID NOT NULL REFERENCES public.ak_users(id),
brand_id UUID REFERENCES public.mall_brands(id),
-- 分类信息
category_id UUID NOT NULL REFERENCES public.mall_categories(id),
category_path TEXT[], -- 分类路径,便于查询
-- 基础属性
weight DECIMAL(10,3), -- 重量(kg)
dimensions JSONB, -- 尺寸信息 {长,宽,高}
-- 价格信息
base_price DECIMAL(12,2) NOT NULL, -- 基础价格
market_price DECIMAL(12,2), -- 市场价
cost_price DECIMAL(12,2), -- 成本价
-- 库存信息
stock_quantity INTEGER DEFAULT 0, -- 总库存
available_quantity INTEGER DEFAULT 0, -- 可用库存
reserved_quantity INTEGER DEFAULT 0, -- 预留库存
min_order_quantity INTEGER DEFAULT 1, -- 最小起订量
max_order_quantity INTEGER, -- 最大限购量
-- 状态信息
status VARCHAR(20) DEFAULT 'draft', -- 状态draft/active/inactive/deleted
is_featured BOOLEAN DEFAULT false, -- 是否精选
is_new BOOLEAN DEFAULT false, -- 是否新品
is_hot BOOLEAN DEFAULT false, -- 是否热卖
is_on_sale BOOLEAN DEFAULT false, -- 是否促销
-- 多媒体
main_image_url TEXT, -- 主图
image_urls TEXT[], -- 图片URL数组
video_urls TEXT[], -- 视频URL数组
-- SEO相关
seo_title VARCHAR(200), -- SEO标题
seo_description VARCHAR(500), -- SEO描述
seo_keywords TEXT[], -- SEO关键词
slug VARCHAR(200) UNIQUE, -- URL友好标识
-- 销售统计
view_count INTEGER DEFAULT 0, -- 浏览次数
sale_count INTEGER DEFAULT 0, -- 销售数量
favorite_count INTEGER DEFAULT 0, -- 收藏次数
rating_average DECIMAL(3,2) DEFAULT 0, -- 平均评分
rating_count INTEGER DEFAULT 0, -- 评分次数
-- 时间信息
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
published_at TIMESTAMP WITH TIME ZONE, -- 上架时间
sale_start_at TIMESTAMP WITH TIME ZONE, -- 开售时间
sale_end_at TIMESTAMP WITH TIME ZONE, -- 停售时间
-- 额外信息
tags TEXT[], -- 标签
attributes JSONB DEFAULT '{}', -- 自定义属性
notes TEXT, -- 内部备注
-- 约束
CONSTRAINT chk_price_positive CHECK (base_price >= 0),
CONSTRAINT chk_stock_non_negative CHECK (stock_quantity >= 0),
CONSTRAINT chk_available_stock CHECK (available_quantity >= 0),
CONSTRAINT chk_reserved_stock CHECK (reserved_quantity >= 0),
CONSTRAINT chk_rating_range CHECK (rating_average >= 0 AND rating_average <= 5)
);
-- 商品表索引
CREATE INDEX IF NOT EXISTS idx_mall_products_merchant ON public.mall_products(merchant_id, status);
CREATE INDEX IF NOT EXISTS idx_mall_products_category ON public.mall_products(category_id, status);
CREATE INDEX IF NOT EXISTS idx_mall_products_status ON public.mall_products(status, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_mall_products_featured ON public.mall_products(is_featured, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_mall_products_price ON public.mall_products(base_price, status);
CREATE INDEX IF NOT EXISTS idx_mall_products_sale_count ON public.mall_products(sale_count DESC);
CREATE INDEX IF NOT EXISTS idx_mall_products_rating ON public.mall_products(rating_average DESC, rating_count DESC);
CREATE INDEX IF NOT EXISTS idx_mall_products_code ON public.mall_products(product_code);
CREATE INDEX IF NOT EXISTS idx_mall_products_slug ON public.mall_products(slug);
CREATE INDEX IF NOT EXISTS idx_mall_products_tags ON public.mall_products USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_mall_products_category_path ON public.mall_products USING GIN(category_path);
COMMENT ON TABLE public.mall_products IS '商品基础信息表';
-- ===================================================================
-- 2. 商品SKU表
-- ===================================================================
-- 商品SKU表
CREATE TABLE IF NOT EXISTS public.mall_product_skus (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.mall_products(id) ON DELETE CASCADE,
sku_code VARCHAR(100) UNIQUE NOT NULL, -- SKU编码
-- 规格信息
specification_values JSONB NOT NULL DEFAULT '{}', -- 规格值 {"颜色":"红色","尺寸":"L"}
specification_text VARCHAR(500), -- 规格描述文本
-- 价格库存
price DECIMAL(12,2) NOT NULL, -- SKU价格
cost_price DECIMAL(12,2), -- SKU成本价
stock_quantity INTEGER DEFAULT 0, -- SKU库存
available_quantity INTEGER DEFAULT 0, -- SKU可用库存
reserved_quantity INTEGER DEFAULT 0, -- SKU预留库存
-- SKU属性
weight DECIMAL(10,3), -- SKU重量
barcode VARCHAR(50), -- 条形码
image_url TEXT, -- SKU图片
-- 状态
is_active BOOLEAN DEFAULT true, -- 是否启用
is_default BOOLEAN DEFAULT false, -- 是否默认SKU
-- 销售统计
sale_count INTEGER DEFAULT 0, -- 销售数量
-- 时间
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 约束
CONSTRAINT chk_sku_price_positive CHECK (price >= 0),
CONSTRAINT chk_sku_stock_non_negative CHECK (stock_quantity >= 0)
);
-- SKU表索引
CREATE INDEX IF NOT EXISTS idx_mall_product_skus_product ON public.mall_product_skus(product_id, is_active);
CREATE INDEX IF NOT EXISTS idx_mall_product_skus_code ON public.mall_product_skus(sku_code);
CREATE INDEX IF NOT EXISTS idx_mall_product_skus_barcode ON public.mall_product_skus(barcode);
CREATE INDEX IF NOT EXISTS idx_mall_product_skus_default ON public.mall_product_skus(product_id, is_default);
CREATE INDEX IF NOT EXISTS idx_mall_product_skus_spec ON public.mall_product_skus USING GIN(specification_values);
COMMENT ON TABLE public.mall_product_skus IS '商品SKU表';
-- ===================================================================
-- 3. 商品分类表
-- ===================================================================
-- 商品分类表
CREATE TABLE IF NOT EXISTS public.mall_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(200) NOT NULL, -- 分类名称
slug VARCHAR(200) UNIQUE, -- URL友好标识
description TEXT, -- 分类描述
-- 层级关系
parent_id UUID REFERENCES public.mall_categories(id),
level INTEGER DEFAULT 0, -- 层级0=顶级
path TEXT, -- 路径:/1/2/3
sort_order INTEGER DEFAULT 0, -- 排序
-- 显示信息
icon_url TEXT, -- 分类图标
banner_url TEXT, -- 分类横幅
-- 状态
is_active BOOLEAN DEFAULT true, -- 是否启用
is_featured BOOLEAN DEFAULT false, -- 是否精选
-- 统计
product_count INTEGER DEFAULT 0, -- 商品数量
-- SEO
seo_title VARCHAR(200),
seo_description VARCHAR(500),
seo_keywords TEXT[],
-- 时间
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 自定义属性
attributes JSONB DEFAULT '{}'
);
-- 分类表索引
CREATE INDEX IF NOT EXISTS idx_mall_categories_parent ON public.mall_categories(parent_id, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_categories_level ON public.mall_categories(level, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_categories_active ON public.mall_categories(is_active, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_categories_featured ON public.mall_categories(is_featured, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_categories_slug ON public.mall_categories(slug);
COMMENT ON TABLE public.mall_categories IS '商品分类表';
-- ===================================================================
-- 4. 商品品牌表
-- ===================================================================
-- 商品品牌表
CREATE TABLE IF NOT EXISTS public.mall_brands (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(200) NOT NULL UNIQUE, -- 品牌名称
english_name VARCHAR(200), -- 英文名称
slug VARCHAR(200) UNIQUE, -- URL友好标识
description TEXT, -- 品牌描述
-- 品牌信息
logo_url TEXT, -- 品牌Logo
banner_url TEXT, -- 品牌横幅
website_url TEXT, -- 官网地址
origin_country VARCHAR(100), -- 品牌原产国
founded_year INTEGER, -- 创立年份
-- 状态
is_active BOOLEAN DEFAULT true, -- 是否启用
is_featured BOOLEAN DEFAULT false, -- 是否精选
-- 统计
product_count INTEGER DEFAULT 0, -- 商品数量
-- SEO
seo_title VARCHAR(200),
seo_description VARCHAR(500),
seo_keywords TEXT[],
-- 时间
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 排序
sort_order INTEGER DEFAULT 0
);
-- 品牌表索引
CREATE INDEX IF NOT EXISTS idx_mall_brands_active ON public.mall_brands(is_active, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_brands_featured ON public.mall_brands(is_featured, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_brands_slug ON public.mall_brands(slug);
COMMENT ON TABLE public.mall_brands IS '商品品牌表';
-- ===================================================================
-- 5. 商品规格相关表
-- ===================================================================
-- 规格名表(如:颜色、尺寸、款式等)
CREATE TABLE IF NOT EXISTS public.mall_specifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL, -- 规格名称:颜色、尺寸等
slug VARCHAR(100) UNIQUE, -- URL友好标识
type VARCHAR(50) DEFAULT 'select', -- 类型select/input/color/image
sort_order INTEGER DEFAULT 0, -- 排序
is_required BOOLEAN DEFAULT false, -- 是否必选
is_active BOOLEAN DEFAULT true, -- 是否启用
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 规格值表红色、蓝色、L、XL等
CREATE TABLE IF NOT EXISTS public.mall_specification_values (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
specification_id UUID NOT NULL REFERENCES public.mall_specifications(id) ON DELETE CASCADE,
value VARCHAR(200) NOT NULL, -- 规格值红色、L等
color_code VARCHAR(20), -- 颜色代码(仅颜色规格)
image_url TEXT, -- 规格值图片
sort_order INTEGER DEFAULT 0, -- 排序
is_active BOOLEAN DEFAULT true, -- 是否启用
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(specification_id, value)
);
-- 商品规格关联表
CREATE TABLE IF NOT EXISTS public.mall_product_specifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.mall_products(id) ON DELETE CASCADE,
specification_id UUID NOT NULL REFERENCES public.mall_specifications(id) ON DELETE CASCADE,
is_required BOOLEAN DEFAULT false, -- 该商品的该规格是否必选
sort_order INTEGER DEFAULT 0, -- 在该商品中的排序
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(product_id, specification_id)
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_mall_specifications_active ON public.mall_specifications(is_active, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_specification_values_spec ON public.mall_specification_values(specification_id, is_active, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_product_specifications_product ON public.mall_product_specifications(product_id, sort_order);
-- ===================================================================
-- 6. 商品详情相关表
-- ===================================================================
-- 商品详情内容表(富文本、图文混排)
CREATE TABLE IF NOT EXISTS public.mall_product_details (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.mall_products(id) ON DELETE CASCADE,
-- 详情内容
detail_type VARCHAR(50) DEFAULT 'rich_text', -- 类型rich_text/markdown/html
content TEXT, -- 详情内容
images TEXT[], -- 详情图片
-- 显示控制
section_title VARCHAR(200), -- 区块标题
sort_order INTEGER DEFAULT 0, -- 排序
is_active BOOLEAN DEFAULT true, -- 是否显示
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 商品参数表
CREATE TABLE IF NOT EXISTS public.mall_product_attributes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_id UUID NOT NULL REFERENCES public.mall_products(id) ON DELETE CASCADE,
-- 参数信息
attribute_name VARCHAR(200) NOT NULL, -- 参数名称
attribute_value TEXT NOT NULL, -- 参数值
attribute_group VARCHAR(100), -- 参数分组
-- 显示控制
sort_order INTEGER DEFAULT 0, -- 排序
is_key_attribute BOOLEAN DEFAULT false, -- 是否关键参数
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(product_id, attribute_name)
);
-- 索引
CREATE INDEX IF NOT EXISTS idx_mall_product_details_product ON public.mall_product_details(product_id, sort_order);
CREATE INDEX IF NOT EXISTS idx_mall_product_attributes_product ON public.mall_product_attributes(product_id, attribute_group, sort_order);
-- ===================================================================
-- 7. 视图和函数
-- ===================================================================
-- 商品列表视图(包含完整信息)
CREATE OR REPLACE VIEW public.vw_mall_products_full AS
SELECT
p.*,
c.name as category_name,
c.path as category_full_path,
b.name as brand_name,
b.logo_url as brand_logo_url,
-- SKU汇总信息
(SELECT MIN(price) FROM public.mall_product_skus WHERE product_id = p.id AND is_active = true) as min_price,
(SELECT MAX(price) FROM public.mall_product_skus WHERE product_id = p.id AND is_active = true) as max_price,
(SELECT SUM(stock_quantity) FROM public.mall_product_skus WHERE product_id = p.id AND is_active = true) as total_stock,
-- 默认SKU信息
default_sku.id as default_sku_id,
default_sku.sku_code as default_sku_code,
default_sku.price as default_price,
default_sku.stock_quantity as default_stock
FROM public.mall_products p
LEFT JOIN public.mall_categories c ON p.category_id = c.id
LEFT JOIN public.mall_brands b ON p.brand_id = b.id
LEFT JOIN public.mall_product_skus default_sku ON p.id = default_sku.product_id AND default_sku.is_default = true
WHERE p.status != 'deleted';
COMMENT ON VIEW public.vw_mall_products_full IS '商品完整信息视图';
-- ===================================================================
-- 8. 触发器(维护统计数据)
-- ===================================================================
-- 更新商品SKU统计的触发器函数
CREATE OR REPLACE FUNCTION public.update_product_sku_stats()
RETURNS TRIGGER AS $$
BEGIN
-- 更新商品的库存统计
UPDATE public.mall_products
SET
stock_quantity = (
SELECT COALESCE(SUM(stock_quantity), 0)
FROM public.mall_product_skus
WHERE product_id = COALESCE(NEW.product_id, OLD.product_id) AND is_active = true
),
available_quantity = (
SELECT COALESCE(SUM(available_quantity), 0)
FROM public.mall_product_skus
WHERE product_id = COALESCE(NEW.product_id, OLD.product_id) AND is_active = true
),
updated_at = NOW()
WHERE id = COALESCE(NEW.product_id, OLD.product_id);
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
DO $$
BEGIN
DROP TRIGGER IF EXISTS trigger_update_product_sku_stats ON public.mall_product_skus;
CREATE TRIGGER trigger_update_product_sku_stats
AFTER INSERT OR UPDATE OR DELETE ON public.mall_product_skus
FOR EACH ROW EXECUTE FUNCTION public.update_product_sku_stats();
END
$$;
-- ===================================================================
-- 9. 初始化数据
-- ===================================================================
-- 插入基础商品分类
INSERT INTO public.mall_categories (name, slug, level, sort_order) VALUES
('服装鞋包', 'fashion', 0, 1),
('数码家电', 'electronics', 0, 2),
('食品生鲜', 'food', 0, 3),
('家居日用', 'home', 0, 4),
('美妆护肤', 'beauty', 0, 5),
('运动户外', 'sports', 0, 6),
('图书文娱', 'books', 0, 7),
('医药保健', 'health', 0, 8)
ON CONFLICT (slug) DO NOTHING;
-- 插入基础规格
INSERT INTO public.mall_specifications (name, slug, type, sort_order) VALUES
('颜色', 'color', 'color', 1),
('尺寸', 'size', 'select', 2),
('款式', 'style', 'select', 3),
('容量', 'capacity', 'select', 4),
('材质', 'material', 'select', 5)
ON CONFLICT (slug) DO NOTHING;
-- 输出完成信息
DO $$
BEGIN
RAISE NOTICE '商品管理数据库结构创建完成!';
RAISE NOTICE '已创建以下核心表:';
RAISE NOTICE '- mall_products: 商品基础信息';
RAISE NOTICE '- mall_product_skus: 商品SKU';
RAISE NOTICE '- mall_categories: 商品分类';
RAISE NOTICE '- mall_brands: 商品品牌';
RAISE NOTICE '- mall_specifications: 商品规格';
RAISE NOTICE '可以开始添加商品数据了!';
END
$$;

View File

@@ -1,249 +0,0 @@
-- ====================================================================
-- 角色字段统一说明
-- ====================================================================
-- 注意:角色信息统一存储在 ak_users.role 字段中
-- ml_user_profiles 表不再包含 role 字段,避免数据重复
-- 本脚本主要用于清理可能存在的重复字段和更新相关函数
-- ====================================================================
\echo '检查角色字段统一状态...'
BEGIN;
-- ====================================================================
-- 1. 安全检查
-- ====================================================================
-- 检查表是否存在
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ml_user_profiles') THEN
RAISE EXCEPTION '表 ml_user_profiles 不存在,请先运行完整数据库创建脚本';
END IF;
END $$;
-- 检查是否已经有 role 字段
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ml_user_profiles' AND column_name = 'role') THEN
RAISE NOTICE '检测到 role 字段已存在,跳过字段创建';
ELSE
RAISE NOTICE '开始添加 role 字段';
-- 添加 role 字段
ALTER TABLE public.ml_user_profiles
ADD COLUMN role TEXT DEFAULT 'customer';
END IF;
END $$;
-- ====================================================================
-- 2. 数据迁移
-- ====================================================================
-- 迁移现有 user_type 数据到 role 字段
UPDATE public.ml_user_profiles
SET role = CASE
WHEN user_type = 1 THEN 'customer' -- 消费者
WHEN user_type = 2 THEN 'merchant' -- 商家
WHEN user_type = 3 THEN 'delivery' -- 配送员
WHEN user_type = 4 THEN 'service' -- 客服
WHEN user_type = 5 THEN 'admin' -- 管理员
ELSE 'customer'
END
WHERE role = 'customer' OR role IS NULL;
-- 设置非空约束
ALTER TABLE public.ml_user_profiles
ALTER COLUMN role SET NOT NULL;
-- ====================================================================
-- 3. 约束和索引更新
-- ====================================================================
-- 添加新的约束
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints WHERE constraint_name = 'chk_ml_user_role') THEN
ALTER TABLE public.ml_user_profiles
ADD CONSTRAINT chk_ml_user_role
CHECK (role IN ('customer', 'merchant', 'delivery', 'service', 'admin'));
RAISE NOTICE '已添加 role 字段约束';
END IF;
END $$;
-- 创建新索引
DROP INDEX IF EXISTS idx_ml_user_profiles_role;
CREATE INDEX idx_ml_user_profiles_role ON public.ml_user_profiles(role);
-- ====================================================================
-- 4. 同步 ak_users 表的 role 字段
-- ====================================================================
-- 同步 ak_users.role 字段
UPDATE public.ak_users
SET role = p.role,
updated_at = CURRENT_TIMESTAMP
FROM public.ml_user_profiles p
WHERE ak_users.id = p.user_id
AND (ak_users.role != p.role OR ak_users.role IS NULL);
-- ====================================================================
-- 5. 更新函数和视图
-- ====================================================================
-- 更新商家验证函数
CREATE OR REPLACE FUNCTION public.is_verified_merchant(user_uuid UUID)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
result BOOLEAN := FALSE;
BEGIN
SELECT (role = 'merchant' AND verification_status = 1) INTO result
FROM public.ml_user_profiles
WHERE user_id = user_uuid;
RETURN COALESCE(result, FALSE);
END;
$$;
-- 更新用户信息视图
CREATE OR REPLACE VIEW public.ml_users_view AS
SELECT
u.id,
u.email,
u.username,
u.phone,
u.avatar_url,
u.status as user_status,
u.gender,
u.birthday,
u.bio,
u.created_at as user_created_at,
u.updated_at as user_updated_at,
p.role,
p.status,
p.real_name,
p.credit_score,
p.verification_status,
p.created_at as profile_created_at,
p.updated_at as profile_updated_at,
CASE
WHEN p.role = 'customer' THEN '消费者'
WHEN p.role = 'merchant' THEN '商家'
WHEN p.role = 'delivery' THEN '配送员'
WHEN p.role = 'service' THEN '客服'
WHEN p.role = 'admin' THEN '管理员'
ELSE '未知'
END as role_name
FROM public.ak_users u
LEFT JOIN public.ml_user_profiles p ON u.id = p.user_id;
-- ====================================================================
-- 6. 更新字段注释
-- ====================================================================
COMMENT ON COLUMN public.ml_user_profiles.role IS '用户角色customer消费者, merchant商家, delivery配送员, service客服, admin管理员';
-- ====================================================================
-- 7. 验证迁移结果
-- ====================================================================
DO $$
DECLARE
total_users INTEGER;
migrated_users INTEGER;
role_stats RECORD;
BEGIN
-- 统计总用户数
SELECT COUNT(*) INTO total_users FROM public.ml_user_profiles;
-- 统计已迁移用户数
SELECT COUNT(*) INTO migrated_users
FROM public.ml_user_profiles
WHERE role IN ('customer', 'merchant', 'delivery', 'service', 'admin');
RAISE NOTICE '迁移完成:总用户 %, 已迁移 %', total_users, migrated_users;
-- 显示角色分布
RAISE NOTICE '角色分布统计:';
FOR role_stats IN
SELECT role, COUNT(*) as count
FROM public.ml_user_profiles
GROUP BY role
ORDER BY count DESC
LOOP
RAISE NOTICE ' %: % 用户', role_stats.role, role_stats.count;
END LOOP;
END $$;
COMMIT;
\echo '角色字段迁移完成!'
-- ====================================================================
-- 8. 可选:清理旧字段(请谨慎执行)
-- ====================================================================
/*
-- 警告:以下操作将永久删除 user_type 字段,请确保迁移成功后再执行
BEGIN;
-- 删除旧约束
ALTER TABLE public.ml_user_profiles DROP CONSTRAINT IF EXISTS chk_ml_user_type;
-- 删除旧索引
DROP INDEX IF EXISTS idx_ml_user_profiles_type;
-- 删除旧字段
ALTER TABLE public.ml_user_profiles DROP COLUMN IF EXISTS user_type;
COMMIT;
\echo '旧 user_type 字段清理完成';
*/
-- ====================================================================
-- 9. 回滚脚本(如需回滚,请执行以下命令)
-- ====================================================================
/*
-- 回滚到 user_type 字段(仅在必要时执行)
BEGIN;
-- 重新添加 user_type 字段
ALTER TABLE public.ml_user_profiles
ADD COLUMN user_type INTEGER DEFAULT 1;
-- 从 role 字段恢复数据
UPDATE public.ml_user_profiles
SET user_type = CASE
WHEN role = 'customer' THEN 1
WHEN role = 'merchant' THEN 2
WHEN role = 'delivery' THEN 3
WHEN role = 'service' THEN 4
WHEN role = 'admin' THEN 5
ELSE 1
END;
-- 设置非空约束
ALTER TABLE public.ml_user_profiles
ALTER COLUMN user_type SET NOT NULL;
-- 重新添加约束
ALTER TABLE public.ml_user_profiles
ADD CONSTRAINT chk_ml_user_type CHECK (user_type IN (1,2,3,4,5));
-- 重新创建索引
CREATE INDEX idx_ml_user_profiles_type ON public.ml_user_profiles(user_type);
-- 删除 role 字段
ALTER TABLE public.ml_user_profiles DROP CONSTRAINT IF EXISTS chk_ml_user_role;
ALTER TABLE public.ml_user_profiles DROP COLUMN IF EXISTS role;
COMMIT;
\echo '已回滚到 user_type 字段';
*/

View File

@@ -1,207 +0,0 @@
-- ====================================================================
-- 角色字段清理脚本 - Role Field Cleanup
-- ====================================================================
-- 目的:确保角色信息只存储在 ak_users.role 字段中
-- 清理 ml_user_profiles 表中可能存在的重复 role 字段
-- 兼容性Supabase + PostgreSQL 14+
-- ====================================================================
\echo '开始角色字段清理...'
BEGIN;
-- ====================================================================
-- 1. 检查并清理 ml_user_profiles 中的 role 字段
-- ====================================================================
-- 检查是否存在重复的 role 字段
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ml_user_profiles'
AND column_name = 'role') THEN
RAISE NOTICE '发现 ml_user_profiles 表中存在 role 字段,开始清理...';
-- 如果 ak_users.role 字段为空,从 ml_user_profiles.role 迁移数据
UPDATE public.ak_users
SET role = COALESCE(ak_users.role, p.role),
updated_at = CURRENT_TIMESTAMP
FROM public.ml_user_profiles p
WHERE ak_users.id = p.user_id
AND (ak_users.role IS NULL OR ak_users.role = '');
-- 删除相关约束
ALTER TABLE public.ml_user_profiles DROP CONSTRAINT IF EXISTS chk_ml_user_role;
-- 删除相关索引
DROP INDEX IF EXISTS idx_ml_user_profiles_role;
-- 删除 role 字段
ALTER TABLE public.ml_user_profiles DROP COLUMN IF EXISTS role;
RAISE NOTICE '已删除 ml_user_profiles 表中的 role 字段';
ELSE
RAISE NOTICE 'ml_user_profiles 表中不存在 role 字段,无需清理';
END IF;
END $$;
-- ====================================================================
-- 2. 更新相关函数
-- ====================================================================
-- 更新商家验证函数
CREATE OR REPLACE FUNCTION public.is_verified_merchant(user_uuid UUID)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
result BOOLEAN := FALSE;
BEGIN
SELECT (u.role = 'merchant' AND p.verification_status = 1) INTO result
FROM public.ml_user_profiles p
JOIN public.ak_users u ON p.user_id = u.id
WHERE p.user_id = user_uuid;
RETURN COALESCE(result, FALSE);
END;
$$;
-- 获取用户角色函数
CREATE OR REPLACE FUNCTION public.get_user_role(user_uuid UUID)
RETURNS TEXT
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
user_role TEXT;
BEGIN
SELECT role INTO user_role
FROM public.ak_users
WHERE id = user_uuid;
RETURN COALESCE(user_role, 'customer');
END;
$$;
-- 检查用户权限函数
CREATE OR REPLACE FUNCTION public.check_user_permission(user_uuid UUID, required_roles TEXT[])
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
user_role TEXT;
BEGIN
SELECT role INTO user_role
FROM public.ak_users
WHERE id = user_uuid;
RETURN user_role = ANY(required_roles);
END;
$$;
-- ====================================================================
-- 3. 更新视图
-- ====================================================================
-- 更新用户信息视图
CREATE OR REPLACE VIEW public.ml_users_view AS
SELECT
u.id,
u.email,
u.username,
u.phone,
u.avatar_url,
u.status as user_status,
u.gender,
u.birthday,
u.bio,
u.created_at as user_created_at,
u.updated_at as user_updated_at,
u.role,
p.status,
p.real_name,
p.credit_score,
p.verification_status,
p.created_at as profile_created_at,
p.updated_at as profile_updated_at,
CASE
WHEN u.role = 'customer' THEN '消费者'
WHEN u.role = 'merchant' THEN '商家'
WHEN u.role = 'delivery' THEN '配送员'
WHEN u.role = 'service' THEN '客服'
WHEN u.role = 'admin' THEN '管理员'
ELSE '未知'
END as role_name
FROM public.ak_users u
LEFT JOIN public.ml_user_profiles p ON u.id = p.user_id;
-- 创建角色统计视图
CREATE OR REPLACE VIEW public.vw_role_statistics AS
SELECT
role,
COUNT(*) as user_count,
COUNT(*) * 100.0 / SUM(COUNT(*)) OVER() as percentage
FROM public.ak_users
WHERE role IS NOT NULL
GROUP BY role
ORDER BY user_count DESC;
-- ====================================================================
-- 4. 确保数据一致性
-- ====================================================================
-- 确保所有用户都有角色
UPDATE public.ak_users
SET role = 'customer'
WHERE role IS NULL OR role = '';
-- 确保角色字段有约束
DO $$
BEGIN
-- 检查约束是否存在
IF NOT EXISTS (SELECT 1 FROM information_schema.check_constraints
WHERE constraint_name = 'chk_ak_users_role') THEN
ALTER TABLE public.ak_users
ADD CONSTRAINT chk_ak_users_role
CHECK (role IN ('customer', 'merchant', 'delivery', 'service', 'admin'));
RAISE NOTICE '已添加 ak_users.role 字段约束';
END IF;
END $$;
-- 创建角色字段索引(如果不存在)
CREATE INDEX IF NOT EXISTS idx_ak_users_role ON public.ak_users(role);
COMMIT;
\echo '角色字段清理完成!'
-- ====================================================================
-- 验证结果
-- ====================================================================
-- 检查角色分布
SELECT '角色分布统计:' as info;
SELECT * FROM public.vw_role_statistics;
-- 检查是否还有重复字段
SELECT '字段检查:' as info;
SELECT
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ml_user_profiles'
AND column_name = 'role')
THEN '❌ ml_user_profiles.role 字段仍然存在'
ELSE '✅ ml_user_profiles.role 字段已清理'
END as ml_user_profiles_check,
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ak_users'
AND column_name = 'role')
THEN '✅ ak_users.role 字段存在'
ELSE '❌ ak_users.role 字段不存在'
END as ak_users_check;
SELECT '角色字段统一完成!角色信息统一存储在 ak_users.role 字段中。' as result;

View File

@@ -1,287 +0,0 @@
-- ====================================================================
-- 角色字段统一升级脚本 - Role Field Unification Upgrade
-- ====================================================================
-- 目的:将所有表的 user_type (INTEGER) 字段统一为 role (TEXT) 字段
-- 兼容性Supabase + PostgreSQL 14+
-- 执行顺序:在现有数据库基础上执行
-- ====================================================================
BEGIN;
-- ====================================================================
-- 1. 统一 ml_user_profiles 表的角色字段
-- ====================================================================
-- 1.1 添加新的 role 字段
ALTER TABLE public.ml_user_profiles
ADD COLUMN IF NOT EXISTS role TEXT DEFAULT 'customer';
-- 1.2 将现有 user_type 数据迁移到 role 字段
UPDATE public.ml_user_profiles
SET role = CASE
WHEN user_type = 1 THEN 'customer' -- 消费者
WHEN user_type = 2 THEN 'merchant' -- 商家
WHEN user_type = 3 THEN 'delivery' -- 配送员
WHEN user_type = 4 THEN 'service' -- 客服
WHEN user_type = 5 THEN 'admin' -- 管理员
ELSE 'customer'
END
WHERE role IS NULL OR role = 'customer';
-- 1.3 设置 role 字段约束
ALTER TABLE public.ml_user_profiles
ALTER COLUMN role SET NOT NULL;
ALTER TABLE public.ml_user_profiles
ADD CONSTRAINT IF NOT EXISTS chk_ml_user_role
CHECK (role IN ('customer', 'merchant', 'delivery', 'service', 'admin'));
-- 1.4 更新索引
DROP INDEX IF EXISTS idx_ml_user_profiles_type;
CREATE INDEX IF NOT EXISTS idx_ml_user_profiles_role ON public.ml_user_profiles(role);
-- 1.5 删除旧的 user_type 字段和约束(可选,建议在测试确认后执行)
-- ALTER TABLE public.ml_user_profiles DROP CONSTRAINT IF EXISTS chk_ml_user_type;
-- ALTER TABLE public.ml_user_profiles DROP COLUMN IF EXISTS user_type;
-- ====================================================================
-- 2. 更新相关函数中的字段引用
-- ====================================================================
-- 2.1 更新商家验证函数
CREATE OR REPLACE FUNCTION public.is_verified_merchant(user_uuid UUID)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
result BOOLEAN := FALSE;
BEGIN
SELECT (role = 'merchant' AND verification_status = 1) INTO result
FROM public.ml_user_profiles
WHERE user_id = user_uuid;
RETURN COALESCE(result, FALSE);
END;
$$;
-- 2.2 更新用户信息视图
CREATE OR REPLACE VIEW public.vw_user_info AS
SELECT
u.id as user_id,
u.email,
u.username,
u.role as user_role,
u.status as user_status,
u.created_at as user_created_at,
p.cid as profile_cid,
p.role as profile_role,
p.status as profile_status,
p.real_name,
p.avatar_url,
p.phone,
p.credit_score,
p.verification_status,
p.created_at as profile_created_at,
CASE
WHEN p.role = 'customer' THEN '消费者'
WHEN p.role = 'merchant' THEN '商家'
WHEN p.role = 'delivery' THEN '配送员'
WHEN p.role = 'service' THEN '客服'
WHEN p.role = 'admin' THEN '管理员'
ELSE '未知'
END as role_name
FROM public.ak_users u
LEFT JOIN public.ml_user_profiles p ON u.id = p.user_id;
-- ====================================================================
-- 3. 更新 RLS 策略中的角色检查
-- ====================================================================
-- 3.1 更新商品相关策略
DROP POLICY IF EXISTS "商家管理自己的商品" ON public.ml_products;
CREATE POLICY "商家管理自己的商品"
ON public.ml_products
FOR ALL
TO authenticated
USING (
merchant_id = auth.uid()
OR EXISTS (
SELECT 1 FROM public.ml_user_profiles p
WHERE p.user_id = auth.uid()
AND p.role IN ('admin', 'service')
)
);
-- 3.2 更新订单相关策略
DROP POLICY IF EXISTS "配送员查看分配的订单" ON public.ml_orders;
CREATE POLICY "配送员查看分配的订单"
ON public.ml_orders
FOR SELECT
TO authenticated
USING (
delivery_id = auth.uid()
OR EXISTS (
SELECT 1 FROM public.ml_user_profiles p
WHERE p.user_id = auth.uid()
AND p.role IN ('admin', 'service')
)
);
-- 3.3 更新用户资料策略
DROP POLICY IF EXISTS "用户管理自己的资料" ON public.ml_user_profiles;
CREATE POLICY "用户管理自己的资料"
ON public.ml_user_profiles
FOR ALL
TO authenticated
USING (
user_id = auth.uid()
OR EXISTS (
SELECT 1 FROM public.ml_user_profiles p
WHERE p.user_id = auth.uid()
AND p.role IN ('admin', 'service')
)
);
-- ====================================================================
-- 4. 更新字段注释
-- ====================================================================
COMMENT ON COLUMN public.ml_user_profiles.role IS '用户角色customer消费者, merchant商家, delivery配送员, service客服, admin管理员';
-- ====================================================================
-- 5. 创建角色辅助函数
-- ====================================================================
-- 5.1 获取用户角色函数
CREATE OR REPLACE FUNCTION public.get_user_role(user_uuid UUID)
RETURNS TEXT
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
user_role TEXT;
BEGIN
SELECT role INTO user_role
FROM public.ml_user_profiles
WHERE user_id = user_uuid;
RETURN COALESCE(user_role, 'customer');
END;
$$;
-- 5.2 检查用户权限函数
CREATE OR REPLACE FUNCTION public.check_user_permission(user_uuid UUID, required_roles TEXT[])
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
user_role TEXT;
BEGIN
SELECT role INTO user_role
FROM public.ml_user_profiles
WHERE user_id = user_uuid;
RETURN user_role = ANY(required_roles);
END;
$$;
-- 5.3 角色升级函数(将用户提升为商家等)
CREATE OR REPLACE FUNCTION public.upgrade_user_role(user_uuid UUID, new_role TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- 检查新角色是否有效
IF new_role NOT IN ('customer', 'merchant', 'delivery', 'service', 'admin') THEN
RAISE EXCEPTION '无效的角色类型: %', new_role;
END IF;
-- 更新用户角色
UPDATE public.ml_user_profiles
SET role = new_role,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = user_uuid;
-- 同步更新 ak_users 表的 role 字段
UPDATE public.ak_users
SET role = new_role,
updated_at = CURRENT_TIMESTAMP
WHERE id = user_uuid;
RETURN FOUND;
END;
$$;
-- ====================================================================
-- 6. 数据一致性检查
-- ====================================================================
-- 6.1 检查角色字段一致性
DO $$
DECLARE
inconsistent_count INTEGER;
BEGIN
SELECT COUNT(*) INTO inconsistent_count
FROM public.ak_users u
JOIN public.ml_user_profiles p ON u.id = p.user_id
WHERE u.role != p.role;
IF inconsistent_count > 0 THEN
RAISE NOTICE '发现 % 条记录的角色字段不一致,正在同步...', inconsistent_count;
-- 以 ml_user_profiles.role 为准同步到 ak_users.role
UPDATE public.ak_users
SET role = p.role,
updated_at = CURRENT_TIMESTAMP
FROM public.ml_user_profiles p
WHERE ak_users.id = p.user_id
AND ak_users.role != p.role;
RAISE NOTICE '角色字段同步完成';
ELSE
RAISE NOTICE '角色字段一致性检查通过';
END IF;
END;
$$;
-- ====================================================================
-- 7. 创建角色统计视图
-- ====================================================================
CREATE OR REPLACE VIEW public.vw_role_statistics AS
SELECT
role,
COUNT(*) as user_count,
COUNT(*) * 100.0 / SUM(COUNT(*)) OVER() as percentage
FROM public.ml_user_profiles
GROUP BY role
ORDER BY user_count DESC;
COMMIT;
-- ====================================================================
-- 执行验证
-- ====================================================================
-- 检查角色分布
SELECT '角色分布统计:' as info;
SELECT * FROM public.vw_role_statistics;
-- 检查索引
SELECT '索引检查:' as info;
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'ml_user_profiles'
AND indexname LIKE '%role%';
-- 检查约束
SELECT '约束检查:' as info;
SELECT conname, pg_get_constraintdef(oid) as definition
FROM pg_constraint
WHERE conrelid = 'public.ml_user_profiles'::regclass
AND conname LIKE '%role%';
SELECT '角色字段统一升级完成!' as result;

View File

@@ -1,273 +0,0 @@
-- 商城系统用户兼容性实施方案
-- 基于混合方案:复用 ak_users 主表 + 商城扩展表
-- 1. 商城用户扩展表
CREATE TABLE public.mall_user_profiles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid UNIQUE REFERENCES public.ak_users(id) ON DELETE CASCADE,
user_type INTEGER DEFAULT 1, -- 1:消费者 2:商家 3:配送员 4:客服 5:管理员
status INTEGER DEFAULT 1, -- 1:正常 2:冻结 3:注销 4:待审核
real_name VARCHAR(64), -- 真实姓名(商家认证、配送员必填)
id_card VARCHAR(32), -- 身份证号(商家认证、配送员必填)
credit_score INTEGER DEFAULT 100, -- 信用分数 0-1000
mall_role VARCHAR(32) DEFAULT 'consumer', -- 商城角色标识
verification_status INTEGER DEFAULT 0, -- 认证状态 0:未认证 1:已认证 2:认证失败
verification_data JSONB, -- 认证相关数据
business_license VARCHAR(128), -- 营业执照号(商家)
shop_category VARCHAR(64), -- 店铺类别(商家)
service_areas JSONB, -- 服务区域(配送员)
emergency_contact VARCHAR(128), -- 紧急联系人(配送员)
preferences JSONB, -- 用户偏好设置
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.mall_user_profiles IS '商城用户扩展信息表';
COMMENT ON COLUMN public.mall_user_profiles.user_id IS '关联ak_users表的用户ID';
COMMENT ON COLUMN public.mall_user_profiles.user_type IS '用户类型1消费者 2商家 3配送员 4客服 5管理员';
COMMENT ON COLUMN public.mall_user_profiles.status IS '用户状态1正常 2冻结 3注销 4待审核';
COMMENT ON COLUMN public.mall_user_profiles.credit_score IS '信用分数,影响交易权限';
COMMENT ON COLUMN public.mall_user_profiles.verification_status IS '认证状态0未认证 1已认证 2认证失败';
-- 创建索引
CREATE INDEX idx_mall_user_profiles_user_id ON public.mall_user_profiles(user_id);
CREATE INDEX idx_mall_user_profiles_user_type ON public.mall_user_profiles(user_type);
CREATE INDEX idx_mall_user_profiles_status ON public.mall_user_profiles(status);
CREATE INDEX idx_mall_user_profiles_mall_role ON public.mall_user_profiles(mall_role);
-- 2. 用户地址表
CREATE TABLE public.ak_user_addresses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE,
receiver_name VARCHAR(64) NOT NULL, -- 收货人姓名
receiver_phone VARCHAR(32) NOT NULL, -- 收货人手机
province VARCHAR(64) NOT NULL, -- 省份
city VARCHAR(64) NOT NULL, -- 城市
district VARCHAR(64) NOT NULL, -- 区县
address_detail TEXT NOT NULL, -- 详细地址
postal_code VARCHAR(16), -- 邮编
is_default BOOLEAN DEFAULT false, -- 是否默认地址
label VARCHAR(32), -- 地址标签home/office/school/other
coordinates POINT, -- 经纬度坐标,用于配送距离计算
delivery_instructions TEXT, -- 配送说明
business_hours VARCHAR(128), -- 可配送时间(如9:00-18:00)
status INTEGER DEFAULT 1, -- 地址状态1正常 2禁用
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.ak_user_addresses IS '用户地址表';
COMMENT ON COLUMN public.ak_user_addresses.coordinates IS '经纬度坐标格式POINT(longitude latitude)';
COMMENT ON COLUMN public.ak_user_addresses.label IS '地址标签home家 office公司 school学校 other其他';
-- 创建索引
CREATE INDEX idx_user_addresses_user_id ON public.ak_user_addresses(user_id);
CREATE INDEX idx_user_addresses_city ON public.ak_user_addresses(city);
CREATE INDEX idx_user_addresses_district ON public.ak_user_addresses(district);
CREATE INDEX idx_user_addresses_is_default ON public.ak_user_addresses(is_default);
-- 创建地理位置索引(用于附近配送查询)
CREATE INDEX idx_user_addresses_coordinates ON public.ak_user_addresses USING GIST(coordinates);
-- 3. 用户收藏表
CREATE TABLE public.mall_user_favorites (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE,
target_type VARCHAR(32) NOT NULL, -- 收藏类型product/shop
target_id uuid NOT NULL, -- 目标ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.mall_user_favorites IS '用户收藏表';
COMMENT ON COLUMN public.mall_user_favorites.target_type IS '收藏类型product商品 shop店铺';
-- 创建索引和唯一约束
CREATE INDEX idx_mall_user_favorites_user_id ON public.mall_user_favorites(user_id);
CREATE INDEX idx_mall_user_favorites_target ON public.mall_user_favorites(target_type, target_id);
CREATE UNIQUE INDEX idx_mall_user_favorites_unique ON public.mall_user_favorites(user_id, target_type, target_id);
-- 4. 用户搜索历史表
CREATE TABLE public.mall_user_search_history (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE,
keyword VARCHAR(256) NOT NULL, -- 搜索关键词
search_count INTEGER DEFAULT 1, -- 搜索次数
last_search_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.mall_user_search_history IS '用户搜索历史表';
-- 创建索引
CREATE INDEX idx_mall_search_history_user_id ON public.mall_user_search_history(user_id);
CREATE INDEX idx_mall_search_history_keyword ON public.mall_user_search_history(keyword);
CREATE UNIQUE INDEX idx_mall_search_history_unique ON public.mall_user_search_history(user_id, keyword);
-- 5. 用户浏览历史表
CREATE TABLE public.mall_user_browse_history (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE,
product_id uuid NOT NULL, -- 浏览的商品ID
browse_count INTEGER DEFAULT 1, -- 浏览次数
browse_duration INTEGER DEFAULT 0, -- 浏览时长(秒)
last_browse_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.mall_user_browse_history IS '用户浏览历史表';
-- 创建索引
CREATE INDEX idx_mall_browse_history_user_id ON public.mall_user_browse_history(user_id);
CREATE INDEX idx_mall_browse_history_product_id ON public.mall_user_browse_history(product_id);
CREATE INDEX idx_mall_browse_history_last_browse ON public.mall_user_browse_history(last_browse_at);
CREATE UNIQUE INDEX idx_mall_browse_history_unique ON public.mall_user_browse_history(user_id, product_id);
-- 6. 触发器:确保每个用户只有一个默认地址
CREATE OR REPLACE FUNCTION ensure_single_default_address()
RETURNS TRIGGER AS $$
BEGIN
-- 如果新插入/更新的地址设为默认
IF NEW.is_default = true THEN
-- 将该用户的其他地址的默认状态设为false
UPDATE public.ak_user_addresses
SET is_default = false
WHERE user_id = NEW.user_id AND id != NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
CREATE TRIGGER trigger_ensure_single_default_address
BEFORE INSERT OR UPDATE ON public.ak_user_addresses
FOR EACH ROW
EXECUTE FUNCTION ensure_single_default_address();
-- 7. 触发器:自动更新 updated_at 字段
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为相关表创建更新时间触发器
CREATE TRIGGER trigger_mall_user_profiles_updated_at
BEFORE UPDATE ON public.mall_user_profiles
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER trigger_user_addresses_updated_at
BEFORE UPDATE ON public.ak_user_addresses
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 8. 数据迁移:为现有 ak_users 用户创建默认商城档案
INSERT INTO public.mall_user_profiles (user_id, user_type, status, mall_role)
SELECT
id,
1, -- 默认为消费者
1, -- 默认状态正常
'consumer' -- 默认角色消费者
FROM public.ak_users
WHERE id NOT IN (SELECT user_id FROM public.mall_user_profiles WHERE user_id IS NOT NULL);
-- 9. 创建视图:商城用户完整信息视图
CREATE VIEW public.mall_users_view AS
SELECT
u.id,
u.username,
u.email,
u.phone,
u.avatar_url,
u.gender,
u.birthday,
u.bio,
u.created_at as user_created_at,
u.updated_at as user_updated_at,
mp.user_type,
mp.status,
mp.real_name,
mp.credit_score,
mp.mall_role,
mp.verification_status,
mp.created_at as profile_created_at,
mp.updated_at as profile_updated_at
FROM public.ak_users u
INNER JOIN public.mall_user_profiles mp ON u.id = mp.user_id;
COMMENT ON VIEW public.mall_users_view IS '商城用户完整信息视图';
-- 10. 权限设置(根据实际需要调整)
-- 创建商城相关的RLS策略
ALTER TABLE public.mall_user_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ak_user_addresses ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.mall_user_favorites ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.mall_user_search_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.mall_user_browse_history ENABLE ROW LEVEL SECURITY;
-- 用户只能访问自己的数据
CREATE POLICY mall_user_profiles_policy ON public.mall_user_profiles
FOR ALL USING (auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id));
CREATE POLICY user_addresses_policy ON public.ak_user_addresses
FOR ALL USING (auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id));
CREATE POLICY mall_user_favorites_policy ON public.mall_user_favorites
FOR ALL USING (auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id));
CREATE POLICY mall_user_search_history_policy ON public.mall_user_search_history
FOR ALL USING (auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id));
CREATE POLICY mall_user_browse_history_policy ON public.mall_user_browse_history
FOR ALL USING (auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id));
-- 11. 示例查询函数
-- 获取用户默认地址
CREATE OR REPLACE FUNCTION get_user_default_address(p_user_id uuid)
RETURNS TABLE (
id uuid,
receiver_name varchar,
receiver_phone varchar,
full_address text,
coordinates point
) AS $$
BEGIN
RETURN QUERY
SELECT
a.id,
a.receiver_name,
a.receiver_phone,
(a.province || a.city || a.district || a.address_detail) as full_address,
a.coordinates
FROM public.ak_user_addresses a
WHERE a.user_id = p_user_id AND a.is_default = true AND a.status = 1
LIMIT 1;
END;
$$ LANGUAGE plpgsql;
-- 检查用户是否为商城认证商家
CREATE OR REPLACE FUNCTION is_verified_merchant(p_user_id uuid)
RETURNS boolean AS $$
DECLARE
result boolean := false;
BEGIN
SELECT (user_type = 2 AND verification_status = 1) INTO result
FROM public.mall_user_profiles
WHERE user_id = p_user_id;
RETURN COALESCE(result, false);
END;
$$ LANGUAGE plpgsql;
-- 12. 完成提示
DO $$
BEGIN
RAISE NOTICE '商城用户兼容性方案部署完成!';
RAISE NOTICE '已创建表mall_user_profiles, ak_user_addresses, mall_user_favorites, mall_user_search_history, mall_user_browse_history';
RAISE NOTICE '已创建视图mall_users_view';
RAISE NOTICE '已设置触发器和RLS策略';
RAISE NOTICE '已为现有用户创建默认商城档案';
END $$;

View File

@@ -1,113 +0,0 @@
-- 商城数据库脚本验证测试
-- 这个脚本用于验证数据库创建和模拟数据插入是否正常工作
-- 1. 检查必要的扩展是否可用
DO $$
BEGIN
-- 检查 uuid-ossp 扩展
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'uuid-ossp') THEN
RAISE NOTICE 'uuid-ossp 扩展未安装,请先执行: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";';
ELSE
RAISE NOTICE 'uuid-ossp 扩展已安装 ✓';
END IF;
-- 检查 pgcrypto 扩展
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto') THEN
RAISE NOTICE 'pgcrypto 扩展未安装,请先执行: CREATE EXTENSION IF NOT EXISTS "pgcrypto";';
ELSE
RAISE NOTICE 'pgcrypto 扩展已安装 ✓';
END IF;
END $$;
-- 2. 检查 ak_users 表是否存在
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'ak_users') THEN
RAISE NOTICE 'ak_users 表已存在 ✓';
-- 检查 ak_users 表结构
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'ak_users' AND column_name = 'auth_id' AND data_type = 'uuid') THEN
RAISE NOTICE 'ak_users.auth_id 字段类型正确 (uuid) ✓';
ELSE
RAISE NOTICE 'ak_users.auth_id 字段类型可能不正确,应为 uuid 类型';
END IF;
ELSE
RAISE NOTICE 'ak_users 表不存在,需要先创建或从现有系统迁移';
END IF;
END $$;
-- 3. 语法验证 - 测试典型的 RLS 策略语法
DO $$
BEGIN
RAISE NOTICE '开始验证 RLS 策略语法...';
-- 测试 UUID 比较语法
BEGIN
-- 这个查询应该能正常解析
PERFORM 1 WHERE '00000000-0000-0000-0000-000000000000'::uuid = '00000000-0000-0000-0000-000000000000'::uuid;
RAISE NOTICE 'UUID 比较语法正确 ✓';
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'UUID 比较语法错误: %', SQLERRM;
END;
RAISE NOTICE 'RLS 策略语法验证完成 ✓';
END $$;
-- 4. 检查商城表是否已存在
DO $$
DECLARE
table_count INTEGER;
mall_tables TEXT[] := ARRAY[
'ml_user_profiles', 'ml_user_addresses', 'ml_shopping_cart',
'ml_merchants', 'ml_categories', 'ml_products', 'ml_product_images',
'ml_product_variants', 'ml_inventory', 'ml_orders', 'ml_order_items',
'ml_reviews', 'ml_user_behavior', 'ml_promotions', 'ml_coupons',
'ml_user_coupons', 'ml_delivery_info', 'ml_system_config'
];
tbl TEXT;
BEGIN
table_count := 0;
FOREACH tbl IN ARRAY mall_tables
LOOP
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = tbl) THEN
table_count := table_count + 1;
END IF;
END LOOP;
RAISE NOTICE '商城表检查: %/% 个表已存在', table_count, array_length(mall_tables, 1);
IF table_count = 0 THEN
RAISE NOTICE '商城表尚未创建,可以执行 complete_mall_database.sql';
ELSIF table_count = array_length(mall_tables, 1) THEN
RAISE NOTICE '所有商城表已存在 ✓';
ELSE
RAISE NOTICE '部分商城表已存在,建议检查现有表结构';
END IF;
END $$;
-- 5. 模拟数据检查
DO $$
DECLARE
user_count INTEGER;
profile_count INTEGER;
product_count INTEGER;
BEGIN
-- 检查用户数据
SELECT COUNT(*) INTO user_count FROM public.ak_users WHERE username IN ('admin', 'merchant1', 'merchant2', 'customer1', 'customer2', 'customer3', 'driver1', 'driver2');
RAISE NOTICE '测试用户数量: %', user_count;
-- 检查商城相关数据(如果表存在)
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'ml_user_profiles') THEN
SELECT COUNT(*) INTO profile_count FROM public.ml_user_profiles;
RAISE NOTICE '用户档案数量: %', profile_count;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'ml_products') THEN
SELECT COUNT(*) INTO product_count FROM public.ml_products;
RAISE NOTICE '商品数量: %', product_count;
END IF;
END $$;
-- 验证完成
SELECT '数据库验证测试完成' AS status;

View File

@@ -1,113 +0,0 @@
-- =================================================================-- 验证7检查临时表是否已清理
SELECT
'临时表清理检查' as check_type,
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'temp_user_ids')
THEN '临时表仍存在'
ELSE '临时表已清理'
END as cleanup_status;
-- 验证8检查配送任务分配逻辑
SELECT
'配送任务分配检查' as check_type,
COUNT(DISTINCT dt.driver_id) as assigned_drivers,
COUNT(*) as total_tasks,
ROUND(AVG(tasks_per_driver.task_count), 2) as avg_tasks_per_driver
FROM public.ml_delivery_tasks dt
CROSS JOIN (
SELECT driver_id, COUNT(*) as task_count
FROM public.ml_delivery_tasks
GROUP BY driver_id
) as tasks_per_driver;============
-- mock_data_insert.sql 修复验证脚本
-- 用途: 验证修复后的模拟数据插入脚本是否能正常执行
-- =====================================================================================
-- 验证1检查商品价格数据完整性
SELECT
'商品价格检查' as check_type,
COUNT(*) as total_products,
COUNT(CASE WHEN base_price IS NULL THEN 1 END) as null_base_price_count,
COUNT(CASE WHEN base_price > 0 THEN 1 END) as valid_price_count
FROM public.ml_products;
-- 验证2检查SKU价格数据完整性
SELECT
'SKU价格检查' as check_type,
COUNT(*) as total_skus,
COUNT(CASE WHEN price IS NULL THEN 1 END) as null_price_count,
COUNT(CASE WHEN price > 0 THEN 1 END) as valid_price_count
FROM public.ml_product_skus;
-- 验证3测试商品-SKU价格查询逻辑
SELECT
'价格查询逻辑测试' as check_type,
p.name as product_name,
p.base_price,
s.price as sku_price,
COALESCE(s.price, p.base_price) as final_price,
CASE
WHEN s.price IS NOT NULL THEN 'SKU价格'
ELSE '基础价格'
END as price_source
FROM public.ml_products p
LEFT JOIN public.ml_product_skus s ON p.id = s.product_id
ORDER BY p.name, s.sku_code
LIMIT 10;
-- 验证4检查订单商品价格是否存在NULL值
SELECT
'订单商品价格检查' as check_type,
COUNT(*) as total_order_items,
COUNT(CASE WHEN price IS NULL THEN 1 END) as null_price_count,
COUNT(CASE WHEN price > 0 THEN 1 END) as valid_price_count,
MIN(price) as min_price,
MAX(price) as max_price
FROM public.ml_order_items;
-- 验证5检查订单关联的商家ID是否正确
SELECT
'订单商家关联检查' as check_type,
COUNT(DISTINCT o.merchant_id) as unique_merchants,
COUNT(*) as total_orders,
COUNT(CASE WHEN u.role = 'merchant' THEN 1 END) as valid_merchant_orders
FROM public.ml_orders o
LEFT JOIN public.ak_users u ON o.merchant_id = u.id;
-- 验证6检查配送任务唯一性
SELECT
'配送任务唯一性检查' as check_type,
COUNT(*) as total_delivery_tasks,
COUNT(DISTINCT order_id) as unique_orders,
COUNT(*) - COUNT(DISTINCT order_id) as duplicate_order_count,
CASE
WHEN COUNT(*) = COUNT(DISTINCT order_id) THEN '✓ 无重复订单'
ELSE '✗ 存在重复订单配送任务'
END as uniqueness_status
FROM public.ml_delivery_tasks;
-- 验证7检查临时表是否已清理
SELECT
'临时表清理检查' as check_type,
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'temp_user_ids')
THEN '临时表仍存在'
ELSE '临时表已清理'
END as cleanup_status;
-- 输出总体验证结果
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '模拟数据插入脚本修复验证完成';
RAISE NOTICE '=======================================================';
RAISE NOTICE '请检查以上查询结果:';
RAISE NOTICE '1. 商品和SKU价格应无NULL值';
RAISE NOTICE '2. 订单商品价格应无NULL值';
RAISE NOTICE '3. 订单应正确关联到商家用户';
RAISE NOTICE '4. 配送任务应无重复订单';
RAISE NOTICE '5. 临时表应已清理';
RAISE NOTICE '=======================================================';
RAISE NOTICE '如所有检查通过,说明修复有效';
RAISE NOTICE '=======================================================';
END $$;