consumer模块完成90%,前端完成supabase对接
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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 $$;
|
||||
@@ -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 $$;
|
||||
@@ -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 $$;
|
||||
@@ -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 $$;
|
||||
@@ -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
@@ -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
|
||||
$$;
|
||||
@@ -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 字段';
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 $$;
|
||||
@@ -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;
|
||||
@@ -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 $$;
|
||||
Reference in New Issue
Block a user