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

869 lines
34 KiB
PL/PgSQL

-- =====================================================================================
-- 商城系统数据库迁移脚本 (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 $$;