This commit is contained in:
2026-02-03 17:26:23 +08:00
532 changed files with 97923 additions and 20698 deletions

View File

@@ -0,0 +1,24 @@
-- 为用户 b653fded-7d5e-4950-aa0d-725595543e3c 添加2张优惠券
-- 基于 ml_coupon_templates 表中有效的模板
INSERT INTO public.ml_user_coupons (
user_id,
template_id,
coupon_code,
status,
received_at,
expire_at
)
SELECT
'b653fded-7d5e-4950-aa0d-725595543e3c'::uuid, -- 目标用户ID
id,
-- 生成随机优惠券码 (CT + 随机字符)
'CT' || upper(substring(md5(random()::text || clock_timestamp()::text) from 1 for 10)),
1, -- 状态: 1 (未使用)
now(),
end_time -- 使用模板的截止时间作为过期时间
FROM
public.ml_coupon_templates
WHERE
status = 1 -- 仅选择状态正常的模板
LIMIT 2;

View File

@@ -0,0 +1,43 @@
-- 1. 创建足迹表
CREATE TABLE IF NOT EXISTS ml_user_footprints (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL DEFAULT auth.uid(),
product_id UUID NOT NULL, -- 如果 ml_products 表存在,可以改为: product_id UUID NOT NULL REFERENCES ml_products(id) ON DELETE CASCADE
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, product_id) -- 复合唯一索引,防止同一商品重复记录
);
-- 2. 添加表注释
COMMENT ON TABLE ml_user_footprints IS '用户浏览足迹表';
COMMENT ON COLUMN ml_user_footprints.user_id IS '用户ID';
COMMENT ON COLUMN ml_user_footprints.product_id IS '商品ID';
COMMENT ON COLUMN ml_user_footprints.updated_at IS '最后访问时间';
-- 3. 启用行级安全策略 (RLS)
ALTER TABLE ml_user_footprints ENABLE ROW LEVEL SECURITY;
-- 4. 添加安全策略 (增删改查仅限本人)
-- 查看策略
CREATE POLICY "Users can view their own footprints"
ON ml_user_footprints FOR SELECT
USING (auth.uid() = user_id);
-- 插入策略
CREATE POLICY "Users can insert their own footprints"
ON ml_user_footprints FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- 更新策略
CREATE POLICY "Users can update their own footprints"
ON ml_user_footprints FOR UPDATE
USING (auth.uid() = user_id);
-- 删除策略
CREATE POLICY "Users can delete their own footprints"
ON ml_user_footprints FOR DELETE
USING (auth.uid() = user_id);
-- 5. 建立索引以优化查询速度
CREATE INDEX IF NOT EXISTS idx_footprints_user_updated
ON ml_user_footprints (user_id, updated_at DESC);

View File

@@ -0,0 +1,98 @@
-- =====================================================================================
-- 补充消息 notification 和 客服 chat 相关表结构及测试数据
-- 对应用户 ID: b653fded-7d5e-4950-aa0d-725595543e3c (test@mall.com)
-- =====================================================================================
-- 1. 创建通知/消息表 (ml_notifications)
-- 用于存储: 系统通知、优惠活动、订单通知等
CREATE TABLE IF NOT EXISTS public.ml_notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- 'system'(系统通知), 'promotion'(优惠活动), 'order'(订单通知)
title VARCHAR(200) NOT NULL,
content TEXT,
icon_url TEXT, -- 图标/图片地址
link_url TEXT, -- 点击跳转链接
is_read BOOLEAN DEFAULT FALSE,
extra_data JSONB DEFAULT '{}', -- 扩展数据
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_notifications IS '系统通知与活动消息表';
-- 开启 RLS
ALTER TABLE public.ml_notifications ENABLE ROW LEVEL SECURITY;
-- 创建 RLS 策略 (用户只能查自己的通知)
CREATE POLICY ml_notifications_select_policy ON public.ml_notifications
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
-- 2. 创建客服会话/消息表 (ml_chat_messages)
-- 用于存储: 用户与客服的聊天记录
CREATE TABLE IF NOT EXISTS public.ml_chat_messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID DEFAULT uuid_generate_v4(), -- 会话ID可用于分组
sender_id UUID REFERENCES public.ak_users(id), -- 发送者ID (NULL代表系统/自动回复)
receiver_id UUID REFERENCES public.ak_users(id), -- 接收者ID
content TEXT,
msg_type VARCHAR(20) DEFAULT 'text', -- 'text', 'image', 'product', 'order'
is_read BOOLEAN DEFAULT FALSE,
is_from_user BOOLEAN DEFAULT FALSE, -- 方便前端判断方向 (true: 用户发给客服, false: 客服发给用户)
extra_data JSONB DEFAULT '{}', -- 用于存储商品卡片信息等
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
COMMENT ON TABLE public.ml_chat_messages IS '客服聊天记录表';
-- 开启 RLS
ALTER TABLE public.ml_chat_messages ENABLE ROW LEVEL SECURITY;
-- 创建 RLS 策略 (用户只能查属于自己的消息)
CREATE POLICY ml_chat_messages_select_policy ON public.ml_chat_messages
FOR SELECT USING (
auth.uid() IN (
SELECT auth_id FROM public.ak_users WHERE id IN (sender_id, receiver_id)
)
);
-- =====================================================================================
-- 3. 插入测试数据
-- 目标用户: b653fded-7d5e-4950-aa0d-725595543e3c
-- =====================================================================================
-- 3.1 插入一条“系统通知”
INSERT INTO public.ml_notifications (user_id, type, title, content, icon_url, created_at)
VALUES (
'b653fded-7d5e-4950-aa0d-725595543e3c',
'system',
'系统安全升级通知',
'尊敬的用户,为了保障您的账户安全,我们已完成系统安全组件升级。建议您定期修改登录密码。',
'/static/icons/system-notice.png',
NOW() - INTERVAL '1 day' -- 昨天收到的
);
-- 3.2 插入一条“优惠活动”
INSERT INTO public.ml_notifications (user_id, type, title, content, icon_url, link_url, created_at)
VALUES (
'b653fded-7d5e-4950-aa0d-725595543e3c',
'promotion',
'夏日清凉大促开启',
'全场商品5折起清凉一夏好物带回家。点击立即参与抢购限量神券等你来拿',
'/static/icons/promotion.png',
'/pages/mall/activity/summer_sale',
NOW()
);
-- 3.3 插入一条“客服消息” (模仿客服发给用户的欢迎语)
INSERT INTO public.ml_chat_messages (sender_id, receiver_id, content, msg_type, is_from_user, created_at)
VALUES (
NULL, -- NULL表示系统客服
'b653fded-7d5e-4950-aa0d-725595543e3c',
'您好欢迎光临商城优选。请问有什么可以帮您的吗我们全天24小时为您服务。',
'text',
FALSE, -- 客服发的
NOW()
);

View File

@@ -0,0 +1,14 @@
-- =====================================================================================
-- 补充订单通知数据
-- 目标用户: b653fded-7d5e-4950-aa0d-725595543e3c
-- =====================================================================================
INSERT INTO public.ml_notifications (user_id, type, title, content, extra_data, created_at)
VALUES (
'b653fded-7d5e-4950-aa0d-725595543e3c',
'order',
'订单已发货',
'您的订单 ML20240203000101 已发货快递单号SF1234567890请保持电话畅通。',
'{"order_no": "ML20240203000101", "status": "shipping", "statusText": "配送中"}',
NOW()
);

View File

@@ -0,0 +1,2 @@
-- 清空购物车数据 (谨慎操作)
TRUNCATE TABLE public.ml_shopping_cart;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
-- Mall Software Subscription Tables
-- PostgreSQL DDL; adjust schema name as needed (default public)
-- Plans
create table if not exists ml_subscription_plans (
id uuid primary key default gen_random_uuid(),
plan_code text not null unique,
name text not null,
description text,
features jsonb,
price numeric(12,2) not null,
currency text default 'CNY',
billing_period text not null check (billing_period in ('monthly','yearly')),
trial_days int default 0,
is_active boolean default true,
sort_order int default 0,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists idx_ml_subscription_plans_active on ml_subscription_plans(is_active) where is_active = true;
create index if not exists idx_ml_subscription_plans_sort on ml_subscription_plans(sort_order);
-- User Subscriptions
create table if not exists ml_user_subscriptions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null,
plan_id uuid not null references ml_subscription_plans(id) on delete restrict,
status text not null default 'active' check (status in ('trial','active','past_due','canceled','expired')),
start_date timestamptz not null default now(),
end_date timestamptz,
next_billing_date timestamptz,
auto_renew boolean not null default true,
cancel_at_period_end boolean not null default false,
metadata jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists idx_ml_user_subscriptions_user on ml_user_subscriptions(user_id);
create index if not exists idx_ml_user_subscriptions_plan on ml_user_subscriptions(plan_id);
create index if not exists idx_ml_user_subscriptions_status on ml_user_subscriptions(status);
-- updated_at trigger helper (idempotent)
create or replace function public.set_updated_at()
returns trigger
language plpgsql
as $fn$
begin
new.updated_at = now();
return new;
end;
$fn$;
-- Recreate triggers safely
drop trigger if exists trg_ml_subscription_plans_updated on ml_subscription_plans;
create trigger trg_ml_subscription_plans_updated
before update on ml_subscription_plans
for each row execute function public.set_updated_at();
drop trigger if exists trg_ml_user_subscriptions_updated on ml_user_subscriptions;
create trigger trg_ml_user_subscriptions_updated
before update on ml_user_subscriptions
for each row execute function public.set_updated_at();
-- Optional: basic RLS scaffolding (customize policies per project standards)
-- alter table ml_user_subscriptions enable row level security;
-- create policy rls_ml_user_subscriptions_owner on ml_user_subscriptions
-- using (user_id::text = current_setting('app.user_id', true));
-- Done

View File

@@ -0,0 +1,150 @@
-- =====================================================================================
-- 修复商品分类关联脚本
-- 说明:根据商品名称关键字,将商品自动关联到正确的分类
-- =====================================================================================
DO $$
DECLARE
-- 一级分类ID
cat_digital UUID;
cat_fashion UUID;
cat_home UUID;
cat_food UUID;
cat_beauty UUID;
-- 二级分类ID
cat_mobile UUID;
cat_computer UUID;
cat_mens UUID;
cat_womens UUID;
cat_snacks UUID;
cat_fruits UUID;
cat_appliances UUID;
-- 计数器
count_updated INTEGER := 0;
BEGIN
-- 1. 获取分类ID (根据名称)
SELECT id INTO cat_digital FROM public.ml_categories WHERE name = '数码电器' LIMIT 1;
SELECT id INTO cat_fashion FROM public.ml_categories WHERE name = '服装鞋帽' LIMIT 1;
SELECT id INTO cat_home FROM public.ml_categories WHERE name = '家居用品' LIMIT 1;
SELECT id INTO cat_food FROM public.ml_categories WHERE name = '食品饮料' LIMIT 1;
SELECT id INTO cat_beauty FROM public.ml_categories WHERE name = '美妆护肤' LIMIT 1;
SELECT id INTO cat_mobile FROM public.ml_categories WHERE name = '手机通讯' LIMIT 1;
SELECT id INTO cat_computer FROM public.ml_categories WHERE name = '电脑办公' LIMIT 1;
SELECT id INTO cat_mens FROM public.ml_categories WHERE name = '男装' LIMIT 1;
SELECT id INTO cat_womens FROM public.ml_categories WHERE name = '女装' LIMIT 1;
SELECT id INTO cat_snacks FROM public.ml_categories WHERE name = '零食坚果' LIMIT 1;
SELECT id INTO cat_fruits FROM public.ml_categories WHERE name = '新鲜水果' LIMIT 1;
SELECT id INTO cat_appliances FROM public.ml_categories WHERE name = '家用电器' LIMIT 1;
RAISE NOTICE '找到分类ID: 数码=%, 手机=%', cat_digital, cat_mobile;
-- 2. 执行更新逻辑
-- 手机/通讯类
IF cat_mobile IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_mobile
WHERE name ILIKE '%iPhone%' OR name ILIKE '%手机%' OR name ILIKE '%Huawei%' OR name ILIKE '%Samsung%' OR name ILIKE '%Xiaomi%' OR name ILIKE '%Pixel%';
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [手机通讯]', count_updated;
END IF;
-- 电脑/办公类
IF cat_computer IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_computer
WHERE name ILIKE '%MacBook%' OR name ILIKE '%Laptop%' OR name ILIKE '%电脑%' OR name ILIKE '%ThinkPad%' OR name ILIKE '%Dell%' OR name ILIKE '%Mouse%' OR name ILIKE '%Keyboard%';
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [电脑办公]', count_updated;
END IF;
-- 兜底:其余数码产品归为一级分类 [数码电器] (如果找不到二级)
IF cat_digital IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_digital
WHERE (category_id IS NULL OR category_id = cat_digital)
AND (name ILIKE '%Camera%' OR name ILIKE '%Headphone%' OR name ILIKE '%Watch%');
END IF;
-- 男装类
IF cat_mens IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_mens
WHERE name ILIKE '%男%' AND (name ILIKE '%T恤%' OR name ILIKE '%Shirt%' OR name ILIKE '%Pants%' OR name ILIKE '%Jacket%' OR name ILIKE '%Suit%');
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [男装]', count_updated;
END IF;
-- 女装类
IF cat_womens IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_womens
WHERE (name ILIKE '%女%' OR name ILIKE '%Dress%' OR name ILIKE '%Skirt%') AND (name ILIKE '%T恤%' OR name ILIKE '%Shirt%' OR name ILIKE '%Pants%' OR name ILIKE '%Jacket%');
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [女装]', count_updated;
END IF;
-- 兜底:其他服装
IF cat_fashion IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_fashion
WHERE category_id IS NULL AND (name ILIKE '%Shirt%' OR name ILIKE '%Wear%' OR name ILIKE '%Clothes%');
END IF;
-- 零食坚果
IF cat_snacks IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_snacks
WHERE name ILIKE '%零食%' OR name ILIKE '%Snack%' OR name ILIKE '%Nut%' OR name ILIKE '%Chocolate%' OR name ILIKE '%Candy%';
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [零食坚果]', count_updated;
END IF;
-- 新鲜水果
IF cat_fruits IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_fruits
WHERE name ILIKE '%果%' OR name ILIKE '%Apple%' OR name ILIKE '%Banana%' OR name ILIKE '%Orange%';
-- 此时要注意排除 "Apple" 手机,所以要加上排除条件
UPDATE public.ml_products
SET category_id = cat_fruits
WHERE (name ILIKE '%Fruit%' OR name ILIKE '%香蕉%' OR name ILIKE '%苹果%')
AND name NOT ILIKE '%iPhone%' AND name NOT ILIKE '%Watch%';
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [新鲜水果]', count_updated;
END IF;
-- 家用电器
IF cat_appliances IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = cat_appliances
WHERE name ILIKE '%TV%' OR name ILIKE '%Fridge%' OR name ILIKE '%Washer%' OR name ILIKE '%Air Conditioner%' OR name ILIKE '%Fan%' OR name ILIKE '%电器%' OR name ILIKE '%冰箱%' OR name ILIKE '%洗衣机%';
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已更新 % 个商品到 [家用电器]', count_updated;
END IF;
-- 3. 处理剩余未分类商品
-- 将剩余未分类的商品随机分配到几个大类中,确保它们能出现在列表中
IF cat_digital IS NOT NULL AND cat_fashion IS NOT NULL AND cat_food IS NOT NULL THEN
UPDATE public.ml_products
SET category_id = CASE floor(random() * 3)::integer
WHEN 0 THEN cat_digital
WHEN 1 THEN cat_fashion
ELSE cat_food
END
WHERE category_id IS NULL; -- 只更新还没分类的
GET DIAGNOSTICS count_updated = ROW_COUNT;
RAISE NOTICE '已随机分配 % 个未分类商品', count_updated;
END IF;
-- 4. 确保所有SKU的库存同步到商品主表 (修复可能的库存显示为0的问题)
UPDATE public.ml_products
SET total_stock = 100, available_stock = 100
WHERE total_stock = 0 OR total_stock IS NULL;
END $$;

View File

@@ -0,0 +1,40 @@
-- =====================================================================================
-- 为没有SKU的商品生成默认SKU
-- 说明购物车逻辑依赖SKU ID如果商品没有SKU数据前端生成的模拟SKU ID会导致数据库外键约束错误
-- =====================================================================================
DO $$
DECLARE
v_product RECORD;
v_sku_id UUID;
v_count INT := 0;
BEGIN
-- 遍历所有没有SKU的商品
FOR v_product IN
SELECT p.id, p.product_code, p.base_price, p.total_stock
FROM public.ml_products p
LEFT JOIN public.ml_product_skus s ON p.id = s.product_id
WHERE s.id IS NULL
LOOP
-- 生成默认SKU
INSERT INTO public.ml_product_skus (
product_id,
sku_code,
specifications,
price,
stock,
status
) VALUES (
v_product.id,
v_product.product_code || '-DEF',
'{"默认": "标准规格"}',
v_product.base_price,
v_product.total_stock,
1
);
v_count := v_count + 1;
END LOOP;
RAISE NOTICE '已为 % 个商品生成默认SKU', v_count;
END $$;

View File

@@ -0,0 +1,88 @@
-- =====================================================================================
-- 批量添加商品数据脚本
-- 用途: 为每个二级分类额外添加6个商品用于测试上拉加载更多功能
-- =====================================================================================
DO $$
DECLARE
cat_rec RECORD;
merchant_rec RECORD;
brand_rec RECORD;
i INT;
v_product_name TEXT;
v_image_url TEXT;
v_price DECIMAL;
BEGIN
-- 1. 获取一个商户ID (优先选择已有店铺的商户)
SELECT merchant_id INTO merchant_rec FROM public.ml_shops LIMIT 1;
IF merchant_rec.merchant_id IS NULL THEN
RAISE NOTICE '未找到商户,请先运行 mock_data_insert.sql 初始化商户数据';
RETURN;
END IF;
-- 2. 获取一个默认品牌ID
SELECT id INTO brand_rec FROM public.ml_brands LIMIT 1;
-- 3. 遍历所有二级分类
FOR cat_rec IN SELECT id, name, slug FROM public.ml_categories WHERE level = 2 LOOP
RAISE NOTICE '正在为分类 [%] 添加商品...', cat_rec.name;
-- 为每个分类添加6个商品
FOR i IN 1..6 LOOP
-- 生成随机价格
v_price := (random() * 500 + 50)::numeric(10, 2);
-- 根据分类名称生成简单的商品名称
v_product_name := cat_rec.name || '精选优品 ' || i || '号 - ' ||
(CASE (i % 3)
WHEN 0 THEN '高品质'
WHEN 1 THEN '超值装'
ELSE '家庭装'
END);
-- 使用随机图片
v_image_url := 'https://picsum.photos/600/600?random=' || floor(random() * 10000)::text;
-- 插入商品
INSERT INTO public.ml_products (
merchant_id,
category_id,
brand_id,
name,
subtitle,
description,
base_price,
market_price,
main_image_url,
status,
is_featured,
is_hot,
sale_count,
total_stock,
available_stock,
product_code
) VALUES (
merchant_rec.merchant_id,
cat_rec.id,
brand_rec.id,
v_product_name,
'热销爆款 限时特惠',
'这是一款测试商品,用于展示分类列表的加载更多功能。优质选材,精心制作,值得信赖。',
v_price,
v_price * 1.2, -- 市场价稍高
v_image_url,
1, -- 1: 上架状态
(random() > 0.8), -- 20%概率推荐
(random() > 0.7), -- 30%概率热销
floor(random() * 1000)::int,
999,
999,
'TEST-' || substring(md5(random()::text) from 1 for 8)
);
END LOOP;
END LOOP;
RAISE NOTICE '数据生成完成!';
END $$;

View File

@@ -0,0 +1,281 @@
-- =====================================================================================
-- 补充缺失分类的测试商品数据 (修正版:忽略已存在的 Product Code)
-- 说明为用户提到的缺失分类各生成6个商品数据如果商品已存在则跳过
-- =====================================================================================
DO $$
DECLARE
-- 商家ID
v_merchant_id UUID;
-- 分类ID变量
cat_kitchen UUID; -- 厨具
cat_books UUID; -- 图书文娱
cat_wshoes UUID; -- 女鞋
cat_furniture UUID; -- 家具
cat_home UUID; -- 家居用品
cat_decoration UUID; -- 家装
cat_accessories UUID; -- 数码配件
cat_clothing UUID; -- 服装鞋帽
cat_baby UUID; -- 母婴用品
cat_daily UUID; -- 生活用品
cat_mshoes UUID; -- 男鞋
cat_beauty UUID; -- 美容护肤 (美妆护肤)
cat_meat UUID; -- 肉禽蛋类
cat_sports UUID; -- 运动户外
cat_drinks UUID; -- 酒水饮料
cat_food UUID; -- 食品饮料
BEGIN
-- 1. 获取商家ID
SELECT id INTO v_merchant_id FROM public.ak_users WHERE role = 'merchant' LIMIT 1;
IF v_merchant_id IS NULL THEN
SELECT id INTO v_merchant_id FROM public.ak_users LIMIT 1;
END IF;
-- 2. 获取分类ID (尝试匹配名称)
SELECT id INTO cat_kitchen FROM public.ml_categories WHERE name = '厨具' LIMIT 1;
SELECT id INTO cat_books FROM public.ml_categories WHERE name = '图书文娱' LIMIT 1;
SELECT id INTO cat_wshoes FROM public.ml_categories WHERE name = '女鞋' LIMIT 1;
SELECT id INTO cat_furniture FROM public.ml_categories WHERE name = '家具' LIMIT 1;
SELECT id INTO cat_home FROM public.ml_categories WHERE name = '家居用品' LIMIT 1;
SELECT id INTO cat_decoration FROM public.ml_categories WHERE name = '家装' OR name = '家装建材' LIMIT 1;
SELECT id INTO cat_accessories FROM public.ml_categories WHERE name = '数码配件' LIMIT 1;
SELECT id INTO cat_clothing FROM public.ml_categories WHERE name = '服装鞋帽' LIMIT 1;
SELECT id INTO cat_baby FROM public.ml_categories WHERE name = '母婴用品' LIMIT 1;
SELECT id INTO cat_daily FROM public.ml_categories WHERE name = '生活用品' LIMIT 1;
SELECT id INTO cat_mshoes FROM public.ml_categories WHERE name = '男鞋' LIMIT 1;
SELECT id INTO cat_beauty FROM public.ml_categories WHERE name = '美容护肤' OR name = '美妆护肤' LIMIT 1;
SELECT id INTO cat_meat FROM public.ml_categories WHERE name = '肉禽蛋类' LIMIT 1;
SELECT id INTO cat_sports FROM public.ml_categories WHERE name = '运动户外' LIMIT 1;
SELECT id INTO cat_drinks FROM public.ml_categories WHERE name = '酒水饮料' LIMIT 1;
SELECT id INTO cat_food FROM public.ml_categories WHERE name = '食品饮料' LIMIT 1;
-- 3. 插入数据 (添加 ON CONFLICT DO NOTHING)
-- ---------------------------------------------------------------------------------
-- 厨具
-- ---------------------------------------------------------------------------------
IF cat_kitchen IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_kitchen, 'KIT001', '苏泊尔红点不粘锅炒锅', '少油烟易清洗30cm', '/static/images/demo/p1.jpg', 199.00, 200, 200, 1, 800),
(v_merchant_id, cat_kitchen, 'KIT002', '十八子作切片刀', '家用菜刀,不锈钢', '/static/images/demo/p2.jpg', 89.00, 300, 300, 1, 1200),
(v_merchant_id, cat_kitchen, 'KIT003', '双立人刀具套装', '德国工艺,多功能', '/static/images/demo/p3.jpg', 699.00, 100, 100, 1, 300),
(v_merchant_id, cat_kitchen, 'KIT004', '楠竹防霉菜板', '整竹加厚,双面可用', '/static/images/demo/p4.jpg', 49.00, 500, 500, 1, 2000),
(v_merchant_id, cat_kitchen, 'KIT005', '不锈钢厨房置物架', '多层收纳,节省空间', '/static/images/demo/p5.jpg', 129.00, 150, 150, 1, 600),
(v_merchant_id, cat_kitchen, 'KIT006', '硅胶锅铲套装', '耐高温,不伤锅', '/static/images/demo/p6.jpg', 39.00, 400, 400, 1, 1500)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 图书文娱
-- ---------------------------------------------------------------------------------
IF cat_books IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_books, 'BOOK001', '《三体》全集 刘慈欣著', '中国科幻基石', '/static/images/demo/p1.jpg', 68.00, 500, 500, 1, 5000),
(v_merchant_id, cat_books, 'BOOK002', '尤克里里初学者套装', '23寸桃花芯木', '/static/images/demo/p2.jpg', 199.00, 200, 200, 1, 800),
(v_merchant_id, cat_books, 'BOOK003', '儿童绘画套装礼盒', '水彩笔蜡笔,安全无毒', '/static/images/demo/p3.jpg', 59.00, 300, 300, 1, 1500),
(v_merchant_id, cat_books, 'BOOK004', '《活着》 余华著', '当代文学经典', '/static/images/demo/p4.jpg', 35.00, 600, 600, 1, 3000),
(v_merchant_id, cat_books, 'BOOK005', '乐高积木 机械组', '跑车模型,益智拼装', '/static/images/demo/p5.jpg', 899.00, 100, 100, 1, 400),
(v_merchant_id, cat_books, 'BOOK006', '雅马哈民谣吉他', 'F310新手入门推荐', '/static/images/demo/p6.jpg', 999.00, 50, 50, 1, 200)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 女鞋
-- ---------------------------------------------------------------------------------
IF cat_wshoes IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_wshoes, 'WSHOE001', '法式尖头高跟鞋', '细跟浅口,气质通勤', '/static/images/demo/p1.jpg', 299.00, 100, 100, 1, 500),
(v_merchant_id, cat_wshoes, 'WSHOE002', '百搭小白鞋', '真皮休闲,舒适透气', '/static/images/demo/p2.jpg', 159.00, 300, 300, 1, 1200),
(v_merchant_id, cat_wshoes, 'WSHOE003', '英伦风马丁靴', '厚底增高,帅气机车', '/static/images/demo/p3.jpg', 359.00, 150, 150, 1, 800),
(v_merchant_id, cat_wshoes, 'WSHOE004', '平底乐福鞋', '金属扣装饰,软底', '/static/images/demo/p4.jpg', 199.00, 200, 200, 1, 600),
(v_merchant_id, cat_wshoes, 'WSHOE005', '夏季凉鞋', '一字扣带,粗跟', '/static/images/demo/p5.jpg', 129.00, 400, 400, 1, 900),
(v_merchant_id, cat_wshoes, 'WSHOE006', '秋冬过膝长靴', '弹力绒面,显瘦修腿', '/static/images/demo/p6.jpg', 499.00, 80, 80, 1, 300)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 家具
-- ---------------------------------------------------------------------------------
IF cat_furniture IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_furniture, 'FURN001', '北欧布艺沙发', '三人位,可拆洗,小户型', '/static/images/demo/p1.jpg', 2999.00, 50, 50, 1, 100),
(v_merchant_id, cat_furniture, 'FURN002', '实木双人床', '1.8米大床,现代简约', '/static/images/demo/p2.jpg', 3599.00, 30, 30, 1, 80),
(v_merchant_id, cat_furniture, 'FURN003', '岩板餐桌椅组合', '轻奢风,耐磨耐高温', '/static/images/demo/p3.jpg', 1999.00, 40, 40, 1, 120),
(v_merchant_id, cat_furniture, 'FURN004', '人体工学电脑椅', '可升降,护腰透气', '/static/images/demo/p4.jpg', 699.00, 100, 100, 1, 500),
(v_merchant_id, cat_furniture, 'FURN005', '简约衣柜', '推拉门,大容量储物', '/static/images/demo/p5.jpg', 2599.00, 20, 20, 1, 60),
(v_merchant_id, cat_furniture, 'FURN006', '多层简易鞋柜', '门口家用,防尘大容量', '/static/images/demo/p6.jpg', 199.00, 200, 200, 1, 800)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 家居用品
-- ---------------------------------------------------------------------------------
IF cat_home IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_home, 'HOME001', '纯棉四件套', '60支长绒棉亲肤裸睡', '/static/images/demo/p1.jpg', 299.00, 300, 300, 1, 900),
(v_merchant_id, cat_home, 'HOME002', '乳胶枕头', '泰国天然乳胶,护颈椎', '/static/images/demo/p2.jpg', 159.00, 400, 400, 1, 1500),
(v_merchant_id, cat_home, 'HOME003', '全棉加厚浴巾', '吸水速干,不掉毛', '/static/images/demo/p3.jpg', 59.00, 500, 500, 1, 2000),
(v_merchant_id, cat_home, 'HOME004', '布艺收纳箱', '可折叠,衣物整理', '/static/images/demo/p4.jpg', 39.00, 600, 600, 1, 3000),
(v_merchant_id, cat_home, 'HOME005', '防滑无痕衣架', '干湿两用20个装', '/static/images/demo/p5.jpg', 29.90, 800, 800, 1, 5000),
(v_merchant_id, cat_home, 'HOME006', '平板拖把', '免手洗,家用一拖净', '/static/images/demo/p6.jpg', 49.00, 300, 300, 1, 1200)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 家装
-- ---------------------------------------------------------------------------------
IF cat_decoration IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_decoration, 'DECO001', '吸顶灯LED', '简约现代,客厅卧室通用', '/static/images/demo/p1.jpg', 199.00, 200, 200, 1, 500),
(v_merchant_id, cat_decoration, 'DECO002', '3D立体墙贴', '自粘壁纸,防水防潮', '/static/images/demo/p2.jpg', 29.00, 1000, 1000, 1, 3000),
(v_merchant_id, cat_decoration, 'DECO003', '全遮光窗帘', '成品定制,隔热防晒', '/static/images/demo/p3.jpg', 89.00, 300, 300, 1, 800),
(v_merchant_id, cat_decoration, 'DECO004', '北欧风地毯', '客厅茶几垫,可水洗', '/static/images/demo/p4.jpg', 129.00, 150, 150, 1, 400),
(v_merchant_id, cat_decoration, 'DECO005', '装饰挂画', '三联画,现代简约晶瓷画', '/static/images/demo/p5.jpg', 159.00, 200, 200, 1, 600),
(v_merchant_id, cat_decoration, 'DECO006', '静音挂钟', '创意艺术,时尚时钟', '/static/images/demo/p6.jpg', 69.00, 400, 400, 1, 900)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 数码配件
-- ---------------------------------------------------------------------------------
IF cat_accessories IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_accessories, 'ACC001', '20W快充充电头', 'Type-C接口PD协议', '/static/images/demo/p1.jpg', 59.00, 1000, 1000, 1, 5000),
(v_merchant_id, cat_accessories, 'ACC002', '液态硅胶手机壳', '全包防摔,手感亲肤', '/static/images/demo/p2.jpg', 29.00, 2000, 2000, 1, 8000),
(v_merchant_id, cat_accessories, 'ACC003', '钢化膜 2片装', '高清防指纹,自动贴膜', '/static/images/demo/p3.jpg', 19.90, 3000, 3000, 1, 10000),
(v_merchant_id, cat_accessories, 'ACC004', '10000mAh充电宝', '迷你便携,双向快充', '/static/images/demo/p4.jpg', 99.00, 500, 500, 1, 2000),
(v_merchant_id, cat_accessories, 'ACC005', '桌面手机支架', '可折叠升降,金属底座', '/static/images/demo/p5.jpg', 39.00, 800, 800, 1, 3000),
(v_merchant_id, cat_accessories, 'ACC006', '数据线三合一', '耐用编织,苹果安卓通用于', '/static/images/demo/p6.jpg', 25.00, 1500, 1500, 1, 4000)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 服装鞋帽 (General / Accessories)
-- ---------------------------------------------------------------------------------
IF cat_clothing IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_clothing, 'CLOTH001', '鸭舌帽', '韩版百搭,刺绣棒球帽', '/static/images/demo/p1.jpg', 39.00, 500, 500, 1, 1200),
(v_merchant_id, cat_clothing, 'CLOTH002', '纯棉袜子 5双装', '中筒袜,吸汗防臭', '/static/images/demo/p2.jpg', 29.90, 1000, 1000, 1, 5000),
(v_merchant_id, cat_clothing, 'CLOTH003', '真皮皮带', '自动扣,商务休闲', '/static/images/demo/p3.jpg', 99.00, 300, 300, 1, 800),
(v_merchant_id, cat_clothing, 'CLOTH004', '羊毛围巾', '冬季保暖,经典格子', '/static/images/demo/p4.jpg', 129.00, 200, 200, 1, 600),
(v_merchant_id, cat_clothing, 'CLOTH005', '触屏手套', '加绒保暖,骑行防风', '/static/images/demo/p5.jpg', 49.00, 400, 400, 1, 900),
(v_merchant_id, cat_clothing, 'CLOTH006', '防晒衣', 'UPF50+,轻薄透气', '/static/images/demo/p6.jpg', 79.00, 600, 600, 1, 2000)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 母婴用品
-- ---------------------------------------------------------------------------------
IF cat_baby IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_baby, 'BABY001', '婴儿纸尿裤 L码', '超薄透气,干爽瞬吸', '/static/images/demo/p1.jpg', 129.00, 500, 500, 1, 3000),
(v_merchant_id, cat_baby, 'BABY002', '婴儿配方奶粉 3段', '进口奶源DHA添加', '/static/images/demo/p2.jpg', 299.00, 200, 200, 1, 1500),
(v_merchant_id, cat_baby, 'BABY003', 'PPSU奶瓶', '宽口径,防胀气', '/static/images/demo/p3.jpg', 89.00, 300, 300, 1, 800),
(v_merchant_id, cat_baby, 'BABY004', '婴儿湿巾 80抽*5', '纯水无添加,手口专用', '/static/images/demo/p4.jpg', 49.90, 1000, 1000, 1, 5000),
(v_merchant_id, cat_baby, 'BABY005', '轻便折叠婴儿推车', '可坐可躺,一键收车', '/static/images/demo/p5.jpg', 599.00, 100, 100, 1, 400),
(v_merchant_id, cat_baby, 'BABY006', '益智积木桌', '多功能游戏桌,大颗粒', '/static/images/demo/p6.jpg', 199.00, 150, 150, 1, 600)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 生活用品
-- ---------------------------------------------------------------------------------
IF cat_daily IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_daily, 'DAILY001', '抽纸 24包整箱', '4层加厚原生木浆', '/static/images/demo/p1.jpg', 59.90, 800, 800, 1, 6000),
(v_merchant_id, cat_daily, 'DAILY002', '洗衣液 3kg+2kg', '深层洁净,薰衣草香', '/static/images/demo/p2.jpg', 49.90, 600, 600, 1, 4000),
(v_merchant_id, cat_daily, 'DAILY003', '洗发水套装', '去屑止痒,柔顺丝滑', '/static/images/demo/p3.jpg', 79.00, 400, 400, 1, 2000),
(v_merchant_id, cat_daily, 'DAILY004', '加厚垃圾袋', '手提式,承重力强', '/static/images/demo/p4.jpg', 19.90, 1000, 1000, 1, 8000),
(v_merchant_id, cat_daily, 'DAILY005', '按压式牙膏', '美白去黄,清新口气', '/static/images/demo/p5.jpg', 29.90, 500, 500, 1, 3000),
(v_merchant_id, cat_daily, 'DAILY006', '透明收纳箱', '特大号,带轮滑', '/static/images/demo/p6.jpg', 45.00, 300, 300, 1, 1200)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 男鞋
-- ---------------------------------------------------------------------------------
IF cat_mshoes IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_mshoes, 'MSHOE001', '商务正装皮鞋', '头层牛皮,透气舒适', '/static/images/demo/p1.jpg', 399.00, 100, 100, 1, 400),
(v_merchant_id, cat_mshoes, 'MSHOE002', '运动跑步鞋', '减震气垫,轻便防滑', '/static/images/demo/p2.jpg', 299.00, 300, 300, 1, 1500),
(v_merchant_id, cat_mshoes, 'MSHOE003', '休闲板鞋', '百搭小白鞋,耐磨', '/static/images/demo/p3.jpg', 159.00, 400, 400, 1, 2000),
(v_merchant_id, cat_mshoes, 'MSHOE004', '高帮工装靴', '复古马丁靴,硬汉风', '/static/images/demo/p4.jpg', 459.00, 150, 150, 1, 600),
(v_merchant_id, cat_mshoes, 'MSHOE005', '夏季网面透气鞋', '镂空飞织,一脚蹬', '/static/images/demo/p5.jpg', 129.00, 500, 500, 1, 1000),
(v_merchant_id, cat_mshoes, 'MSHOE006', '居家防滑拖鞋', 'EVA材质软底静音', '/static/images/demo/p6.jpg', 19.90, 1000, 1000, 1, 5000)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 美容护肤
-- ---------------------------------------------------------------------------------
IF cat_beauty IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_beauty, 'BEAUTY001', '小灯泡美白精华', '提亮肤色,淡化斑点', '/static/images/demo/p1.jpg', 1299.00, 100, 100, 1, 500),
(v_merchant_id, cat_beauty, 'BEAUTY002', '哑光雾面口红', '显白烂番茄色,不拔干', '/static/images/demo/p2.jpg', 299.00, 300, 300, 1, 2000),
(v_merchant_id, cat_beauty, 'BEAUTY003', '氨基酸洗面奶', '温和清洁,敏感肌可用', '/static/images/demo/p3.jpg', 89.00, 1000, 1000, 1, 5000),
(v_merchant_id, cat_beauty, 'BEAUTY004', '补水保湿面膜 10片', '深层补水,急救修护', '/static/images/demo/p4.jpg', 99.00, 800, 800, 1, 4000),
(v_merchant_id, cat_beauty, 'BEAUTY005', '持妆粉底液', '遮瑕控油,不脱妆', '/static/images/demo/p5.jpg', 399.00, 200, 200, 1, 900),
(v_merchant_id, cat_beauty, 'BEAUTY006', '爽肤水', '二次清洁,收缩毛孔', '/static/images/demo/p6.jpg', 159.00, 400, 400, 1, 1200)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 肉禽蛋类
-- ---------------------------------------------------------------------------------
IF cat_meat IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_meat, 'MEAT001', '原切牛排 10片', '整肉原切,眼肉西冷', '/static/images/demo/p1.jpg', 199.00, 500, 500, 1, 2500),
(v_merchant_id, cat_meat, 'MEAT002', '散养土鸡蛋 30枚', '农家散养,营养丰富', '/static/images/demo/p2.jpg', 39.90, 800, 800, 1, 3000),
(v_merchant_id, cat_meat, 'MEAT003', '新鲜五花肉 500g', '肥瘦相间,适合红烧', '/static/images/demo/p3.jpg', 25.00, 300, 300, 1, 1500),
(v_merchant_id, cat_meat, 'MEAT004', '清远鸡 1只', '走地鸡,肉质紧实', '/static/images/demo/p4.jpg', 59.00, 200, 200, 1, 800),
(v_merchant_id, cat_meat, 'MEAT005', '冷冻鸡胸肉 1kg', '低脂高蛋白,健身代餐', '/static/images/demo/p5.jpg', 19.90, 1000, 1000, 1, 5000),
(v_merchant_id, cat_meat, 'MEAT006', '羔羊肉卷 500g', '火锅食材,不膻不腻', '/static/images/demo/p6.jpg', 39.00, 600, 600, 1, 2000)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 运动户外
-- ---------------------------------------------------------------------------------
IF cat_sports IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_sports, 'SPORT001', 'TPE瑜伽垫', '加宽加厚,防滑无味', '/static/images/demo/p1.jpg', 59.00, 500, 500, 1, 2000),
(v_merchant_id, cat_sports, 'SPORT002', '全碳素羽毛球拍', '单拍,超轻进攻型', '/static/images/demo/p2.jpg', 199.00, 200, 200, 1, 600),
(v_merchant_id, cat_sports, 'SPORT003', '标准篮球 7号', '耐磨防滑,室外水泥地', '/static/images/demo/p3.jpg', 89.00, 300, 300, 1, 1000),
(v_merchant_id, cat_sports, 'SPORT004', '可调节哑铃', '家用健身,男女通用', '/static/images/demo/p4.jpg', 129.00, 150, 150, 1, 500),
(v_merchant_id, cat_sports, 'SPORT005', '户外速开帐篷', '防雨防晒3-4人', '/static/images/demo/p5.jpg', 299.00, 100, 100, 1, 300),
(v_merchant_id, cat_sports, 'SPORT006', '跳绳', '专业计数,燃脂减肥', '/static/images/demo/p6.jpg', 29.00, 1000, 1000, 1, 5000)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 酒水饮料
-- ---------------------------------------------------------------------------------
IF cat_drinks IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_drinks, 'DRINK001', '可口可乐 330ml*24', '经典原味,快乐肥宅水', '/static/images/demo/p1.jpg', 45.00, 500, 500, 1, 3000),
(v_merchant_id, cat_drinks, 'DRINK002', '青岛啤酒 500ml*12', '经典10度聚会畅饮', '/static/images/demo/p2.jpg', 59.00, 400, 400, 1, 2000),
(v_merchant_id, cat_drinks, 'DRINK003', '法国进口红酒', '干红葡萄酒,整箱装', '/static/images/demo/p3.jpg', 299.00, 100, 100, 1, 500),
(v_merchant_id, cat_drinks, 'DRINK004', '纯牛奶 250ml*24', '全脂灭菌乳,早餐奶', '/static/images/demo/p4.jpg', 69.00, 600, 600, 1, 4000),
(v_merchant_id, cat_drinks, 'DRINK005', '矿泉水 550ml*24', '天然弱碱性,饮用水', '/static/images/demo/p5.jpg', 29.00, 800, 800, 1, 5000),
(v_merchant_id, cat_drinks, 'DRINK006', '无糖乌龙茶', '0糖0脂解腻茶饮料', '/static/images/demo/p6.jpg', 55.00, 300, 300, 1, 1500)
ON CONFLICT (product_code) DO NOTHING;
END IF;
-- ---------------------------------------------------------------------------------
-- 食品饮料 (General)
-- ---------------------------------------------------------------------------------
IF cat_food IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count) VALUES
(v_merchant_id, cat_food, 'FOOD001', '五常大米 5kg', '东北香米,软糯香甜', '/static/images/demo/p1.jpg', 59.90, 500, 500, 1, 2500),
(v_merchant_id, cat_food, 'FOOD002', '金龙鱼调和油 5L', '家用食用油,非转基因', '/static/images/demo/p2.jpg', 69.90, 400, 400, 1, 3000),
(v_merchant_id, cat_food, 'FOOD003', '方便面整箱 24袋', '红烧牛肉味,夜宵速食', '/static/images/demo/p3.jpg', 45.00, 600, 600, 1, 5000),
(v_merchant_id, cat_food, 'FOOD004', '老干妈风味豆豉', '下饭菜,拌面酱', '/static/images/demo/p4.jpg', 12.90, 1000, 1000, 1, 8000),
(v_merchant_id, cat_food, 'FOOD005', '螺蛳粉 300g*3', '广西特产,酸辣爽口', '/static/images/demo/p5.jpg', 39.90, 800, 800, 1, 6000),
(v_merchant_id, cat_food, 'FOOD006', '广式腊肠 500g', '正宗风味,也是二八肥瘦', '/static/images/demo/p6.jpg', 35.00, 300, 300, 1, 1200)
ON CONFLICT (product_code) DO NOTHING;
END IF;
RAISE NOTICE '数据生成完成:已为缺失分类插入测试商品 (跳过重复)';
END $$;

View File

@@ -0,0 +1,156 @@
-- =====================================================================================
-- 生成特定分类的测试商品数据
-- 说明为主要分类各生成6个真实感的商品数据
-- =====================================================================================
DO $$
DECLARE
-- 商家ID
v_merchant_id UUID;
-- 分类ID
cat_mobile UUID;
cat_computer UUID;
cat_mens UUID;
cat_womens UUID;
cat_snacks UUID;
cat_fruits UUID;
cat_appliances UUID;
cat_beauty UUID;
-- 临时变量
v_product_id UUID;
BEGIN
-- 1. 获取一个商家ID (如果没有商家,就获取任意一个用户)
SELECT id INTO v_merchant_id FROM public.ak_users WHERE role = 'merchant' LIMIT 1;
IF v_merchant_id IS NULL THEN
SELECT id INTO v_merchant_id FROM public.ak_users LIMIT 1;
END IF;
IF v_merchant_id IS NULL THEN
RAISE EXCEPTION '未找到可用用户作为商家';
END IF;
-- 2. 获取分类ID
SELECT id INTO cat_mobile FROM public.ml_categories WHERE name = '手机通讯' LIMIT 1;
SELECT id INTO cat_computer FROM public.ml_categories WHERE name = '电脑办公' LIMIT 1;
SELECT id INTO cat_mens FROM public.ml_categories WHERE name = '男装' LIMIT 1;
SELECT id INTO cat_womens FROM public.ml_categories WHERE name = '女装' LIMIT 1;
SELECT id INTO cat_snacks FROM public.ml_categories WHERE name = '零食坚果' LIMIT 1;
SELECT id INTO cat_fruits FROM public.ml_categories WHERE name = '新鲜水果' LIMIT 1;
SELECT id INTO cat_appliances FROM public.ml_categories WHERE name = '家用电器' LIMIT 1;
SELECT id INTO cat_beauty FROM public.ml_categories WHERE name = '美妆护肤' LIMIT 1;
-- =================================================================================
-- 3. 插入数据
-- =================================================================================
-- ---------------------------------------------------------------------------------
-- 手机通讯 (6个)
-- ---------------------------------------------------------------------------------
IF cat_mobile IS NOT NULL THEN
-- 1. iPhone 15
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB001', 'Apple iPhone 15 Pro Max 256GB', '钛金属边框A17 Pro芯片', '/static/images/demo/p1.jpg', 9999.00, 100, 100, 1, 1205);
-- 2. Huawei Mate 60
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB002', 'Huawei Mate 60 Pro 12GB+512GB', '雅川青超光变XMAGE影像', '/static/images/demo/p2.jpg', 6999.00, 50, 50, 1, 5600);
-- 3. Xiaomi 14
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB003', 'Xiaomi 14 Pro 徕卡可变光圈', '骁龙8Gen3光影猎人900', '/static/images/demo/p3.jpg', 4999.00, 200, 200, 1, 3200);
-- 4. Samsung S24
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB004', 'Samsung Galaxy S24 Ultra', 'AI手机第二代动态AMOLED', '/static/images/demo/p4.jpg', 8899.00, 80, 80, 1, 890);
-- 5. OPPO Find X7
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB005', 'OPPO Find X7 Ultra', '双潜望四主摄,哈苏大师影像', '/static/images/demo/p5.jpg', 5999.00, 120, 120, 1, 1500);
-- 6. Vivo X100
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status, sale_count)
VALUES (v_merchant_id, cat_mobile, 'MOB006', 'vivo X100 Pro 蔡司APO超级长焦', '蓝晶x天玑9300芯片', '/static/images/demo/p6.jpg', 5499.00, 150, 150, 1, 2100);
END IF;
-- ---------------------------------------------------------------------------------
-- 电脑办公 (6个)
-- ---------------------------------------------------------------------------------
IF cat_computer IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_computer, 'COMP001', 'MacBook Pro 14英寸 M3芯片', '太空黑18GB+512GB', '/static/images/demo/p1.jpg', 12999.00, 50, 50, 1),
(v_merchant_id, cat_computer, 'COMP002', 'Lenovo ThinkPad X1 Carbon 2024', '商务轻薄本酷睿Ultra7', '/static/images/demo/p2.jpg', 14999.00, 30, 30, 1),
(v_merchant_id, cat_computer, 'COMP003', 'Dell XPS 15 创作本', '4K OLED触控屏i9处理器', '/static/images/demo/p3.jpg', 18999.00, 20, 20, 1),
(v_merchant_id, cat_computer, 'COMP004', 'Logitech MX Master 3S', '无线蓝牙鼠标,静音滚轮', '/static/images/demo/p4.jpg', 899.00, 500, 500, 1),
(v_merchant_id, cat_computer, 'COMP005', 'Keychron K3 Pro 机械键盘', '矮轴双模QMK/VIA开源', '/static/images/demo/p5.jpg', 468.00, 300, 300, 1),
(v_merchant_id, cat_computer, 'COMP006', 'ASUS ROG 玩家国度 显示器', '4K 144Hz HDR电竞显示器', '/static/images/demo/p6.jpg', 4999.00, 60, 60, 1);
END IF;
-- ---------------------------------------------------------------------------------
-- 男装 (6个)
-- ---------------------------------------------------------------------------------
IF cat_mens IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_mens, 'MEN001', '纯棉重磅T恤男', '宽松美式复古,多色可选', '/static/images/demo/p1.jpg', 89.00, 1000, 1000, 1),
(v_merchant_id, cat_mens, 'MEN002', '商务休闲修身西装外套', '高弹力抗皱,四季款', '/static/images/demo/p2.jpg', 399.00, 200, 200, 1),
(v_merchant_id, cat_mens, 'MEN003', '直筒水洗牛仔裤', '经典五袋款YKK拉链', '/static/images/demo/p3.jpg', 199.00, 500, 500, 1),
(v_merchant_id, cat_mens, 'MEN004', '速干运动短裤', '透气网眼,跑步健身', '/static/images/demo/p4.jpg', 69.00, 800, 800, 1),
(v_merchant_id, cat_mens, 'MEN005', '美利奴羊毛衫', 'V领针织保暖舒适', '/static/images/demo/p5.jpg', 299.00, 150, 150, 1),
(v_merchant_id, cat_mens, 'MEN006', '工装连帽夹克', '防风防水,多口袋设计', '/static/images/demo/p6.jpg', 459.00, 120, 120, 1);
END IF;
-- ---------------------------------------------------------------------------------
-- 女装 (6个)
-- ---------------------------------------------------------------------------------
IF cat_womens IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_womens, 'WOMEN001', '法式碎花连衣裙', '收腰显瘦,雪纺面料', '/static/images/demo/p1.jpg', 269.00, 300, 300, 1),
(v_merchant_id, cat_womens, 'WOMEN002', '高腰A字半身裙', '百搭款,防走光内衬', '/static/images/demo/p2.jpg', 159.00, 400, 400, 1),
(v_merchant_id, cat_womens, 'WOMEN003', '真丝缎面衬衫', '高级感通勤,垂坠感好', '/static/images/demo/p3.jpg', 399.00, 100, 100, 1),
(v_merchant_id, cat_womens, 'WOMEN004', '羊绒双面呢大衣', '赫本风中长款,纯手工缝制', '/static/images/demo/p4.jpg', 1299.00, 80, 80, 1),
(v_merchant_id, cat_womens, 'WOMEN005', '紧身弹力小脚裤', '魔术黑裤,修饰腿型', '/static/images/demo/p5.jpg', 129.00, 600, 600, 1),
(v_merchant_id, cat_womens, 'WOMEN006', '宽松慵懒风毛衣', '马海毛混纺,温柔系', '/static/images/demo/p6.jpg', 189.00, 250, 250, 1);
END IF;
-- ---------------------------------------------------------------------------------
-- 零食坚果 (6个)
-- ---------------------------------------------------------------------------------
IF cat_snacks IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_snacks, 'SNACK001', '混合每日坚果 30包', '腰果核桃蔓越莓,营养均衡', '/static/images/demo/p1.jpg', 129.00, 1000, 1000, 1),
(v_merchant_id, cat_snacks, 'SNACK002', '手撕风干牛肉干', '原味/香辣,高蛋白', '/static/images/demo/p2.jpg', 89.00, 500, 500, 1),
(v_merchant_id, cat_snacks, 'SNACK003', '原切薯片 组合装', '黄瓜味/番茄味/原味', '/static/images/demo/p3.jpg', 29.90, 2000, 2000, 1),
(v_merchant_id, cat_snacks, 'SNACK004', '72%黑巧克力', '纯可可脂,入口即化', '/static/images/demo/p4.jpg', 45.00, 300, 300, 1),
(v_merchant_id, cat_snacks, 'SNACK005', '芒果干 500g', '菲律宾进口,酸甜适中', '/static/images/demo/p5.jpg', 39.90, 800, 800, 1),
(v_merchant_id, cat_snacks, 'SNACK006', '网红辣条大礼包', '怀旧零食,麻辣鲜香', '/static/images/demo/p6.jpg', 19.90, 3000, 3000, 1);
END IF;
-- ---------------------------------------------------------------------------------
-- 新鲜水果 (6个)
-- ---------------------------------------------------------------------------------
IF cat_fruits IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_fruits, 'FRUIT001', '智利进口车厘子 JJJ级', '5kg礼盒装个大脆甜', '/static/images/demo/p1.jpg', 299.00, 200, 200, 1),
(v_merchant_id, cat_fruits, 'FRUIT002', '泰国金枕榴莲', '整个约3-4kg包熟包甜', '/static/images/demo/p2.jpg', 189.00, 100, 100, 1),
(v_merchant_id, cat_fruits, 'FRUIT003', '阳光玫瑰青提', '香印葡萄,无籽脆甜', '/static/images/demo/p3.jpg', 49.90, 300, 300, 1),
(v_merchant_id, cat_fruits, 'FRUIT004', '新疆阿克苏冰糖心苹果', '5kg家庭装带糖心', '/static/images/demo/p4.jpg', 39.90, 500, 500, 1),
(v_merchant_id, cat_fruits, 'FRUIT005', '海南红心火龙果', '大果包邮,花青素满满', '/static/images/demo/p5.jpg', 29.90, 600, 600, 1),
(v_merchant_id, cat_fruits, 'FRUIT006', '四川不知火丑橘', '果肉饱满,汁多肉嫩', '/static/images/demo/p6.jpg', 35.00, 400, 400, 1);
END IF;
-- ---------------------------------------------------------------------------------
-- 家用电器 (6个)
-- ---------------------------------------------------------------------------------
IF cat_appliances IS NOT NULL THEN
INSERT INTO public.ml_products (merchant_id, category_id, product_code, name, description, main_image_url, base_price, total_stock, available_stock, status) VALUES
(v_merchant_id, cat_appliances, 'HOME001', '小米米家扫地机器人', '扫拖一体,激光导航', '/static/images/demo/p1.jpg', 1499.00, 200, 200, 1),
(v_merchant_id, cat_appliances, 'HOME002', '戴森无叶风扇空气净化器', '去除甲醛,凉风扇', '/static/images/demo/p2.jpg', 4999.00, 50, 50, 1),
(v_merchant_id, cat_appliances, 'HOME003', '飞利浦空气炸锅', '大容量可视,低脂健康', '/static/images/demo/p3.jpg', 399.00, 300, 300, 1),
(v_merchant_id, cat_appliances, 'HOME004', '九阳破壁机', '静音免手洗,豆浆果汁', '/static/images/demo/p4.jpg', 499.00, 250, 250, 1),
(v_merchant_id, cat_appliances, 'HOME005', '索尼75寸4K液晶电视', 'X90L系列XR认知芯片', '/static/images/demo/p5.jpg', 8999.00, 40, 40, 1),
(v_merchant_id, cat_appliances, 'HOME006', '海尔双开门冰箱', '500L大容量一级能效', '/static/images/demo/p6.jpg', 3299.00, 60, 60, 1);
END IF;
RAISE NOTICE '数据生成完成:已为各分类插入测试商品';
END $$;

View File

@@ -0,0 +1,105 @@
-- =====================================================================================
-- 为测试用户生成地址数据
-- 用户: test@mall.com
-- User ID: b653fded-7d5e-4950-aa0d-725595543e3c
-- =====================================================================================
DO $$
DECLARE
v_user_id UUID := 'b653fded-7d5e-4950-aa0d-725595543e3c';
BEGIN
-- 1. 插入家庭地址 (默认地址)
INSERT INTO public.ml_user_addresses (
user_id,
receiver_name,
receiver_phone,
province,
city,
district,
address_detail,
postal_code,
is_default,
label,
status,
created_at,
updated_at
) VALUES (
v_user_id,
'测试用户',
'13800138000',
'北京市',
'北京市',
'朝阳区',
'三里屯街道太古里北区 1 号楼 101 室',
'100027',
TRUE,
'',
1,
NOW(),
NOW()
);
-- 2. 插入公司地址
INSERT INTO public.ml_user_addresses (
user_id,
receiver_name,
receiver_phone,
province,
city,
district,
address_detail,
postal_code,
is_default,
label,
status,
created_at,
updated_at
) VALUES (
v_user_id,
'测试员',
'13900139000',
'上海市',
'上海市',
'浦东新区',
'陆家嘴环路 1000 号金茂大厦 88 层',
'200120',
FALSE,
'公司',
1,
NOW(),
NOW()
);
-- 3. 插入其他地址
INSERT INTO public.ml_user_addresses (
user_id,
receiver_name,
receiver_phone,
province,
city,
district,
address_detail,
postal_code,
is_default,
label,
status,
created_at,
updated_at
) VALUES (
v_user_id,
'张三',
'13700137000',
'广东省',
'深圳市',
'南山区',
'粤海街道科技园南区虚拟大学园',
'518057',
FALSE,
'学校',
1,
NOW(),
NOW()
);
RAISE NOTICE '地址数据生成完成 user_id: %', v_user_id;
END $$;

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
-- =====================================================================================
-- 模拟多店铺环境:创建第二个商户和店铺,并将部分商品转移过去
-- =====================================================================================
DO $$
DECLARE
v_merchant_1 UUID;
v_merchant_2 UUID;
v_shop_2_id UUID;
v_user_email VARCHAR := 'test_merchant_2@example.com';
BEGIN
-- 1. 获取已存在的第一个商户 (脚本生成的商品属于这个商户)
SELECT id INTO v_merchant_1 FROM public.ak_users WHERE role = 'merchant' LIMIT 1;
IF v_merchant_1 IS NULL THEN
RAISE EXCEPTION '未找到任何商户用户,请先确保基础数据存在';
END IF;
-- 2. 查找或创建第二个商户
-- 尝试找一个不是 v_merchant_1 的用户
SELECT id INTO v_merchant_2 FROM public.ak_users WHERE id != v_merchant_1 LIMIT 1;
-- 如果只有一个用户,我们需要"借用"这个身份或者提示用户
-- 这里为了安全,如果只有一个用户,我们就不强行创建新用户了(因为涉及auth表)
-- 而是尝试将 ak_users 表里可能的普通用户角色临时改为 merchant 来演示
IF v_merchant_2 IS NULL THEN
-- 尝试找一个普通用户
SELECT id INTO v_merchant_2 FROM public.ak_users WHERE role != 'merchant' LIMIT 1;
IF v_merchant_2 IS NOT NULL THEN
-- 升级为商户以便演示
UPDATE public.ak_users SET role = 'merchant' WHERE id = v_merchant_2;
RAISE NOTICE '已将用户 % 临时升级为商户以便演示多店铺', v_merchant_2;
END IF;
END IF;
-- 如果实在找不到第二个用户,无法继续
IF v_merchant_2 IS NULL THEN
RAISE NOTICE '提示:系统中只有一个用户,无法模拟多店铺场景。请先注册第二个用户。';
RETURN;
END IF;
-- 3. 确保第二个商户有店铺
SELECT id INTO v_shop_2_id FROM public.ml_shops WHERE merchant_id = v_merchant_2 LIMIT 1;
IF v_shop_2_id IS NULL THEN
INSERT INTO public.ml_shops (merchant_id, shop_name, status, description)
VALUES (v_merchant_2, '极客数码专营店', 1, '专注于数码产品配件')
RETURNING id INTO v_shop_2_id;
RAISE NOTICE '为第二个商户创建了店铺:极客数码专营店';
ELSE
UPDATE public.ml_shops SET shop_name = '极客数码专营店' WHERE id = v_shop_2_id;
END IF;
-- 4. 将部分商品转移到第二个店铺
-- 转移 "数码配件(ACC)" 和 "图书(BOOK)" 类商品
UPDATE public.ml_products
SET merchant_id = v_merchant_2
WHERE product_code LIKE 'ACC%' OR product_code LIKE 'BOOK%';
RAISE NOTICE '已将 [数码配件] 和 [图书文娱] 类商品转移到第二个店铺。';
RAISE NOTICE '现在购物车中添加这些商品应该会显示在不同的分组下。';
END $$;

View File

@@ -0,0 +1,47 @@
-- Optional guard to restrict non-admin updates on ml_user_subscriptions
-- Purpose: Allow normal users to toggle auto_renew and cancel_at_period_end only.
-- Admins can update any fields.
-- Dependencies: public.is_admin() from subscription_rls_policies.sql
begin;
-- Create or replace the guard function
create or replace function public.enforce_user_sub_update()
returns trigger
language plpgsql
as $$
begin
-- Admin can change anything
if public.is_admin() then
return new;
end if;
-- Owner can only toggle limited fields
if new.user_id = auth.uid() then
-- Revert disallowed fields to old values
new.status := old.status;
new.plan_id := old.plan_id;
new.start_date := old.start_date;
new.end_date := old.end_date;
new.next_billing_date := old.next_billing_date;
new.metadata := old.metadata;
-- Allow: auto_renew, cancel_at_period_end (and updated_at will be set by trigger)
return new;
end if;
-- Neither admin nor owner
raise exception 'Forbidden (not owner)';
end;
$$;
-- Recreate trigger (idempotent)
drop trigger if exists trg_enforce_user_sub_update on public.ml_user_subscriptions;
create trigger trg_enforce_user_sub_update
before update on public.ml_user_subscriptions
for each row execute function public.enforce_user_sub_update();
commit;
-- Usage:
-- 1) Ensure subscription tables and RLS policies are created (see create_mall_subscription_tables.sql, subscription_rls_policies.sql)
-- 2) Run this script to enforce column-level restrictions for non-admins

View File

@@ -0,0 +1,119 @@
-- Subscription RLS and permissions
-- Purpose: Ensure admins can read/write ml_user_subscriptions and ml_subscription_plans;
-- consumers can only access their own subscriptions; everyone can read active plans.
-- Notes:
-- - Designed for Supabase (auth.uid(), auth.jwt()).
-- - Adjust table/column names if they differ in your DB.
-- 1) Helper: identify admin users
-- Prefer JWT app_metadata.role = 'admin' if you set it; fallback to ak_users.user_type = 5
-- (5 corresponds to ADMIN per MALL_USER_TYPE).
create or replace function public.is_admin()
returns boolean
language sql
stable
as $$
select coalesce(
-- Check custom claim from JWT: { app_metadata: { role: 'admin' } }
((auth.jwt() -> 'app_metadata' ->> 'role') = 'admin')
-- Fallback: ak_users.user_type = 5 (cast to text for compatibility), match user by id as text
or exists (
select 1 from public.ak_users u
where u.id::text = auth.uid()::text
and u.user_type::text = '5'
)
, false);
$$;
comment on function public.is_admin is 'Returns true if current JWT/app user is admin by claim or ak_users.user_type=5.';
-- 2) Enable RLS on subscription tables
alter table if exists public.ml_subscription_plans enable row level security;
alter table if exists public.ml_user_subscriptions enable row level security;
grant select on table public.ml_subscription_plans to anon, authenticated;
grant select, insert, update, delete on table public.ml_subscription_plans to authenticated; -- limited by RLS
grant select, insert, update, delete on table public.ml_user_subscriptions to authenticated; -- limited by RLS
-- 4) Policies for ml_subscription_plans
-- 4.1 Everyone can read active plans
drop policy if exists ml_plans_select_active on public.ml_subscription_plans;
create policy ml_plans_select_active
on public.ml_subscription_plans
for select
to anon, authenticated
using (is_active = true);
-- 4.2 Admin can do anything
drop policy if exists ml_plans_admin_all on public.ml_subscription_plans;
create policy ml_plans_admin_all
on public.ml_subscription_plans
for all
to authenticated
using (public.is_admin())
with check (public.is_admin());
-- 5) Policies for ml_user_subscriptions
-- 5.1 Users can see their own subscriptions
drop policy if exists ml_user_subs_select_own on public.ml_user_subscriptions;
create policy ml_user_subs_select_own
on public.ml_user_subscriptions
for select
to authenticated
using (user_id = auth.uid());
-- 5.2 Users can create their own subscriptions (checkout)
drop policy if exists ml_user_subs_insert_own on public.ml_user_subscriptions;
create policy ml_user_subs_insert_own
on public.ml_user_subscriptions
for insert
to authenticated
with check (user_id = auth.uid());
-- 5.3 Users may update their own records (e.g., auto_renew, cancel_at_period_end)
-- NOTE: This allows updating any columns; for stricter control, add a BEFORE UPDATE trigger
-- that restricts column changes for non-admins.
drop policy if exists ml_user_subs_update_own on public.ml_user_subscriptions;
create policy ml_user_subs_update_own
on public.ml_user_subscriptions
for update
to authenticated
using (user_id = auth.uid())
with check (user_id = auth.uid());
-- 5.4 Admin can do anything on user subscriptions
drop policy if exists ml_user_subs_admin_all on public.ml_user_subscriptions;
create policy ml_user_subs_admin_all
on public.ml_user_subscriptions
for all
to authenticated
using (public.is_admin())
with check (public.is_admin());
-- 6) Optional: Trigger to limit non-admin updates to specific fields
-- Uncomment if you want to enforce column-level restrictions
-- create or replace function public.enforce_user_sub_update()
-- returns trigger language plpgsql as $$
-- begin
-- if public.is_admin() then
-- return new; -- admins can change anything
-- end if;
-- -- Only allow toggling auto_renew and cancel_at_period_end for owners
-- if new.user_id = auth.uid() then
-- new.status := old.status;
-- new.plan_id := old.plan_id;
-- new.start_date := old.start_date;
-- new.end_date := old.end_date;
-- new.next_billing_date := old.next_billing_date;
-- -- allow: auto_renew, cancel_at_period_end
-- return new;
-- end if;
-- raise exception 'Forbidden';
-- end $$;
-- drop trigger if exists trg_enforce_user_sub_update on public.ml_user_subscriptions;
-- create trigger trg_enforce_user_sub_update
-- before update on public.ml_user_subscriptions
-- for each row execute function public.enforce_user_sub_update();
-- 7) Safety: ensure no rows are exposed to non-auth users except active plans via select policy above.
-- Admins authenticate as normal users with admin claim or ak_users.user_type=5.

View File

@@ -0,0 +1,58 @@
-- =====================================================================================
-- 补充商品参数数据 (Update Product Attributes)
-- 对应前端 product-detail.uvue 中的药品相关字段
-- 字段存储在 ml_products 表的 attributes JSONB 列中
-- =====================================================================================
-- 1. 更新所有商品,设置默认的通用参数 (避免 NULL)
UPDATE public.ml_products
SET attributes = attributes || '{
"specification": "标准盒装",
"expiry_date": "24个月",
"storage_conditions": "密封,置阴凉干燥处",
"approval_number": "国药准字H20050000"
}'::jsonb
WHERE attributes IS NULL OR attributes = '{}'::jsonb;
-- 2. 针对特定类型的商品设置详细参数 (示例:感冒药/止痛药类)
-- 假设通过名称模糊匹配
UPDATE public.ml_products
SET attributes = attributes || '{
"specification": "0.3g*24粒/盒",
"usage": "口服。一次1-2粒一日3次。",
"side_effects": "偶见恶心、呕吐、皮疹等轻微反应。",
"precautions": "1. 忌烟、酒及辛辣、生冷、油腻食物。\n2. 不宜在服药期间同时服用滋补性中药。",
"expiry_date": "36个月",
"storage_conditions": "密封,防潮",
"approval_number": "国药准字Z44020000"
}'::jsonb
WHERE name LIKE '%胶囊%' OR name LIKE '%感冒%';
-- 3. 针对特定类型的商品设置详细参数 (示例:维生素/保健类)
UPDATE public.ml_products
SET attributes = attributes || '{
"specification": "100片/瓶",
"usage": "每日一次,每次一片,饭后服用。",
"side_effects": "本品耐受性良好,偶见胃肠道不适。",
"precautions": "1. 本品不能代替药物。\n2. 不宜超过推荐量或与同类营养补充剂同时食用。",
"expiry_date": "24个月",
"storage_conditions": "遮光,密闭保存",
"approval_number": "国食健字G20100000"
}'::jsonb
WHERE name LIKE '%维生素%' OR name LIKE '%片%';
-- 4. 针对特定类型的商品设置详细参数 (示例:外用药/口罩)
UPDATE public.ml_products
SET attributes = attributes || '{
"specification": "10片/包",
"usage": "外用,打开包装即可使用。",
"side_effects": "极少数患者可能出现皮肤过敏。",
"precautions": "1. 本品为一次性使用。\n2. 皮肤破损处禁用。",
"expiry_date": "2年",
"storage_conditions": "置于通风干燥处",
"approval_number": "浙械注准20200000"
}'::jsonb
WHERE name LIKE '%口罩%' OR name LIKE '%贴%';
-- 5. 验证更新结果
-- SELECT id, name, attributes FROM public.ml_products LIMIT 5;

View File

@@ -0,0 +1,147 @@
-- =====================================================================================
-- 精细化商品参数数据更新 (Refined Product Attributes Update)
-- 针对 mock-category-data.uts 中的具体商品进行精确更新
-- =====================================================================================
-- 1. 布洛芬缓释胶囊
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.3g*24粒/盒',
'usage', '口服。成人一次1粒一日2次早晚各一次',
'side_effects', '1. 少数病人可出现恶心、呕吐、胃烧灼感或轻度消化不良、胃肠道溃疡及出血。\n2. 少数病人可出现头痛、头晕、耳鸣、视力模糊。',
'precautions', '1. 本品为对症治疗药不宜长期或大量使用用于止痛不得超过5天用于解热不得超过3天。\n2. 必须整粒吞服,不得嚼碎。',
'expiry_date', '24个月',
'storage_conditions', '密封,在干燥处保存',
'approval_number', '国药准字H19991011',
'manufacturer', '修正药业'
)
WHERE name = '布洛芬缓释胶囊';
-- 2. 板蓝根颗粒
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '10g*20袋/包',
'usage', '开水冲服。一次0.5-1袋一日3-4次。',
'side_effects', '尚不明确。',
'precautions', '1. 忌烟、酒及辛辣、生冷、油腻食物。\n2. 不宜在服药期间同时服用滋补性中药。\n3. 高血压、心脏病、糖尿病等慢性病严重者应在医师指导下服用。',
'expiry_date', '24个月',
'storage_conditions', '密封',
'approval_number', '国药准字Z44023485',
'manufacturer', '白云山'
)
WHERE name = '板蓝根颗粒';
-- 3. 连花清瘟胶囊
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.35g*36粒/盒',
'usage', '口服。一次4粒一日3次。',
'side_effects', '上市后监测数据显示本品可见胃肠道不良反应,如恶心、呕吐、腹痛、腹泻等。',
'precautions', '1. 忌烟、酒及辛辣、生冷、油腻食物。\n2. 不宜在服药期间同时服用滋补性中药。\n3. 风寒感冒者不适用。',
'expiry_date', '30个月',
'storage_conditions', '密封,置阴凉处',
'approval_number', '国药准字Z20040063',
'manufacturer', '以岭药业'
)
WHERE name = '连花清瘟胶囊';
-- 4. 对乙酰氨基酚片
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.5g*12片/盒',
'usage', '口服。6-12岁儿童一次0.5片12岁以上儿童及成人一次1片若持续发热或疼痛可间隔4-6小时重复用药一次24小时内不超过4次。',
'side_effects', '偶见皮疹、荨麻疹、药热及粒细胞减少。',
'precautions', '1. 严重肝肾功能不全者禁用。\n2. 除非有医生指导否则不得服用本品超过10天。',
'expiry_date', '36个月',
'storage_conditions', '密封保存',
'approval_number', '国药准字H20056948',
'manufacturer', '强生制药'
)
WHERE name = '对乙酰氨基酚片';
-- 5. 感冒清热颗粒
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '3g*10袋/盒',
'usage', '开水冲服。一次1袋一日2次。',
'side_effects', '尚不明确。',
'precautions', '1. 忌烟、酒及辛辣、生冷、油腻食物。\n2. 发热体温超过38.5℃的患者,应去医院就诊。',
'expiry_date', '24个月',
'storage_conditions', '密封',
'approval_number', '国药准字Z11020356',
'manufacturer', '同仁堂'
)
WHERE name = '感冒清热颗粒';
-- 6. 阿莫西林胶囊
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.25g*24粒/盒',
'usage', '口服。成人一次0.5g2粒每6-8小时1次一日剂量不超过4g。',
'side_effects', '恶心、呕吐、腹泻及假膜性肠炎等胃肠道反应。',
'precautions', '1. 青霉素过敏者禁用。\n2. 用药前必须进行青霉素皮肤试验,阳性者禁用。',
'expiry_date', '24个月',
'storage_conditions', '遮光,密封保存',
'approval_number', '国药准字H13021770',
'manufacturer', '华北制药'
)
WHERE name = '阿莫西林胶囊';
-- 7. 胃康灵胶囊
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.4g*24粒/盒',
'usage', '口服。一次4粒一日3次饭后服用。',
'side_effects', '偶见口干、便秘。',
'precautions', '1. 饮食宜清淡,忌酒及辛辣、生冷、油腻食物。\n2. 孕妇慎用。',
'expiry_date', '24个月',
'storage_conditions', '密封',
'approval_number', '国药准字Z20025068',
'manufacturer', '三九医药'
)
WHERE name = '胃康灵胶囊';
-- 8. 健胃消食片
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.8g*32片/盒',
'usage', '口服可以咀嚼。一次3片一日3次。',
'side_effects', '尚不明确。',
'precautions', '1. 饮食宜清淡,忌酒及辛辣、生冷、油腻食物。\n2. 有高血压、心脏病、肝病、糖尿病、肾病等慢性病严重者应在医师指导下服用。',
'expiry_date', '24个月',
'storage_conditions', '密封',
'approval_number', '国药准字Z20013220',
'manufacturer', '江中制药'
)
WHERE name = '健胃消食片';
-- 9. 蒙脱石散
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '3g*10袋/盒',
'usage', '口服。成人一次1袋一日3次。',
'side_effects', '少数人可能产生轻度便秘。',
'precautions', '1. 治疗急性腹泻时,应注意纠正脱水。\n2. 如需服用其他药物,建议与本品间隔一段时间。',
'expiry_date', '36个月',
'storage_conditions', '密封,干燥处',
'approval_number', '国药准字H19980053',
'manufacturer', '益普生'
)
WHERE name = '蒙脱石散';
-- 10. 云南白药胶囊
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'specification', '0.25g*32粒/盒',
'usage', '口服。一次1-2粒一日4次2至5岁按1/4剂量服用6至12岁按1/2剂量服用',
'side_effects', '极少数患者服药后可能出现过敏性药疹。',
'precautions', '1. 孕妇禁用。\n2. 服药一日内,忌食蚕豆、鱼类及酸冷食物。',
'expiry_date', '36个月',
'storage_conditions', '密封,干燥处',
'approval_number', '国药准字Z53020799',
'manufacturer', '云南白药'
)
WHERE name = '云南白药胶囊';
-- 检查更新情况
-- SELECT name, attributes FROM public.ml_products WHERE name IN ('布洛芬缓释胶囊', '板蓝根颗粒', '连花清瘟胶囊');

View File

@@ -0,0 +1,92 @@
-- =====================================================================================
-- 淘宝/通用电商商品参数数据更新 (Taobao-like Product Attributes Update)
-- 适用于:服饰、数码、美妆、家居等通用电商场景
-- 字段存储在 ml_products 表的 attributes JSONB 列中
-- =====================================================================================
-- 1. 清理现有属性(可选,如果想保留旧数据请注释掉此行)
-- UPDATE public.ml_products SET attributes = '{}'::jsonb;
-- 2. 这里的更新逻辑分为几类,根据商品名称关键字进行匹配。
-- 如果没有匹配到关键字,将应用“通用默认参数”。
-------------------------------------------------------
-- A. 服饰鞋包类 (Clothing & Shoes)
-- 匹配词T恤, 裙, 裤, 衬衫, 外套, 鞋, 帽, 包, 衣
-------------------------------------------------------
UPDATE public.ml_products
SET attributes = attributes || jsonb_build_object(
'brand', '优选品牌',
'material', '纯棉/聚酯纤维',
'season', '四季通用',
'style', '休闲百搭',
'origin', '中国',
'safety_category', 'B类 (直接接触皮肤)',
'washing_instructions', '常规水洗,不可漂白'
)
WHERE name ~ 'T恤|裙|裤|衬衫|外套|鞋|帽|包|衣';
-------------------------------------------------------
-- B. 数码家电类 (Digital & Electronics)
-- 匹配词:手机, 电脑, 相机, 耳机, 壳, 膜, 充电器, 智能
-------------------------------------------------------
UPDATE public.ml_products
SET attributes = attributes || jsonb_build_object(
'brand', '科技先锋',
'model', 'Gen-X Pro',
'color', '黑色',
'warranty', '全国联保1年',
'origin', '中国大陆',
'production_date', '2023年',
'3c_certificate', '2023011606555555'
)
WHERE name ~ '手机|电脑|相机|耳机|壳|膜|充电器|智能|表';
-------------------------------------------------------
-- C. 美妆护肤类 (Beauty & Skincare)
-- 匹配词:霜, 乳, 水, 面膜, 口红, 粉底, 香水, 洗面奶
-------------------------------------------------------
UPDATE public.ml_products
SET attributes = attributes || jsonb_build_object(
'brand', 'BeautyStar',
'specification', '正常规格',
'origin', '法国/中国',
'shelf_life', '3年',
'skin_type', '所有肤质',
'efficacy', '保湿, 补水, 提亮肤色',
'ingredients', '水, 甘油, 透明质酸'
)
WHERE name ~ '霜|乳|水|面膜|口红|粉底|香水|洗面奶';
-------------------------------------------------------
-- D. 食品百货类 (Food & Groceries)
-- 匹配词:零食, 坚果, 茶, 酒, 奶, 饼干, 面, 油
-------------------------------------------------------
UPDATE public.ml_products
SET attributes = attributes || jsonb_build_object(
'brand', '美味日记',
'net_weight', '500g',
'origin', '中国',
'shelf_life', '12个月',
'storage_method', '置于阴凉干燥处',
'production_license', 'SC10100000000000',
'ingredients', '小麦粉, 白砂糖, 植物油'
)
WHERE name ~ '零食|坚果|茶|酒|奶|饼干|面|油';
-------------------------------------------------------
-- E. 通用保底更新 (Fallback)
-- 对没有任何属性的商品,应用通用电商属性
-------------------------------------------------------
UPDATE public.ml_products
SET attributes = jsonb_build_object(
'brand', '严选',
'specification', '标准规格',
'origin', '中国',
'gross_weight', '0.5kg',
'after_sales', '7天无理由退换'
)
WHERE attributes IS NULL OR attributes = '{}'::jsonb;
-- 验证数据
-- SELECT name, attributes FROM public.ml_products LIMIT 10;

View File

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

View File

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

View File

@@ -0,0 +1,113 @@
-- =================================================================-- 验证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 $$;