20260203
This commit is contained in:
24
doc_mall/consumer/sql/add_coupons.sql
Normal file
24
doc_mall/consumer/sql/add_coupons.sql
Normal 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;
|
||||
43
doc_mall/consumer/sql/add_footprints_table.sql
Normal file
43
doc_mall/consumer/sql/add_footprints_table.sql
Normal 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);
|
||||
98
doc_mall/consumer/sql/add_messages_and_notifications.sql
Normal file
98
doc_mall/consumer/sql/add_messages_and_notifications.sql
Normal 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()
|
||||
);
|
||||
14
doc_mall/consumer/sql/add_order_notification.sql
Normal file
14
doc_mall/consumer/sql/add_order_notification.sql
Normal 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()
|
||||
);
|
||||
2
doc_mall/consumer/sql/clean_cart.sql
Normal file
2
doc_mall/consumer/sql/clean_cart.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- 清空购物车数据 (谨慎操作)
|
||||
TRUNCATE TABLE public.ml_shopping_cart;
|
||||
1377
doc_mall/consumer/sql/complete_mall_database.sql
Normal file
1377
doc_mall/consumer/sql/complete_mall_database.sql
Normal file
File diff suppressed because it is too large
Load Diff
71
doc_mall/consumer/sql/create_mall_subscription_tables.sql
Normal file
71
doc_mall/consumer/sql/create_mall_subscription_tables.sql
Normal 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
|
||||
150
doc_mall/consumer/sql/fix_product_categories.sql
Normal file
150
doc_mall/consumer/sql/fix_product_categories.sql
Normal 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 $$;
|
||||
40
doc_mall/consumer/sql/insert_default_skus.sql
Normal file
40
doc_mall/consumer/sql/insert_default_skus.sql
Normal 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 $$;
|
||||
88
doc_mall/consumer/sql/insert_extra_products.sql
Normal file
88
doc_mall/consumer/sql/insert_extra_products.sql
Normal 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 $$;
|
||||
281
doc_mall/consumer/sql/insert_missing_category_products.sql
Normal file
281
doc_mall/consumer/sql/insert_missing_category_products.sql
Normal 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 $$;
|
||||
156
doc_mall/consumer/sql/insert_specific_category_products.sql
Normal file
156
doc_mall/consumer/sql/insert_specific_category_products.sql
Normal 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 $$;
|
||||
105
doc_mall/consumer/sql/insert_test_addresses.sql
Normal file
105
doc_mall/consumer/sql/insert_test_addresses.sql
Normal 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 $$;
|
||||
692
doc_mall/consumer/sql/mall_alter_upgrade.sql
Normal file
692
doc_mall/consumer/sql/mall_alter_upgrade.sql
Normal 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 $$;
|
||||
332
doc_mall/consumer/sql/mall_database_check.sql
Normal file
332
doc_mall/consumer/sql/mall_database_check.sql
Normal 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 $$;
|
||||
734
doc_mall/consumer/sql/mall_fields_only_upgrade.sql
Normal file
734
doc_mall/consumer/sql/mall_fields_only_upgrade.sql
Normal 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 $$;
|
||||
868
doc_mall/consumer/sql/mall_migration.sql
Normal file
868
doc_mall/consumer/sql/mall_migration.sql
Normal 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 $$;
|
||||
666
doc_mall/consumer/sql/mall_seo_security.sql
Normal file
666
doc_mall/consumer/sql/mall_seo_security.sql
Normal 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 $$;
|
||||
1180
doc_mall/consumer/sql/mock_data_insert.sql
Normal file
1180
doc_mall/consumer/sql/mock_data_insert.sql
Normal file
File diff suppressed because it is too large
Load Diff
452
doc_mall/consumer/sql/product_database.sql
Normal file
452
doc_mall/consumer/sql/product_database.sql
Normal 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
|
||||
$$;
|
||||
249
doc_mall/consumer/sql/quick_role_migration.sql
Normal file
249
doc_mall/consumer/sql/quick_role_migration.sql
Normal 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 字段';
|
||||
*/
|
||||
207
doc_mall/consumer/sql/role_field_cleanup.sql
Normal file
207
doc_mall/consumer/sql/role_field_cleanup.sql
Normal 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;
|
||||
287
doc_mall/consumer/sql/role_field_unification.sql
Normal file
287
doc_mall/consumer/sql/role_field_unification.sql
Normal 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;
|
||||
62
doc_mall/consumer/sql/simulate_multi_shop.sql
Normal file
62
doc_mall/consumer/sql/simulate_multi_shop.sql
Normal 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 $$;
|
||||
47
doc_mall/consumer/sql/subscription_guard_trigger.sql
Normal file
47
doc_mall/consumer/sql/subscription_guard_trigger.sql
Normal 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
|
||||
119
doc_mall/consumer/sql/subscription_rls_policies.sql
Normal file
119
doc_mall/consumer/sql/subscription_rls_policies.sql
Normal 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.
|
||||
58
doc_mall/consumer/sql/update_product_attributes.sql
Normal file
58
doc_mall/consumer/sql/update_product_attributes.sql
Normal 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;
|
||||
147
doc_mall/consumer/sql/update_product_attributes_refined.sql
Normal file
147
doc_mall/consumer/sql/update_product_attributes_refined.sql
Normal 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.5g(2粒),每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 ('布洛芬缓释胶囊', '板蓝根颗粒', '连花清瘟胶囊');
|
||||
92
doc_mall/consumer/sql/update_product_attributes_taobao.sql
Normal file
92
doc_mall/consumer/sql/update_product_attributes_taobao.sql
Normal 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;
|
||||
273
doc_mall/consumer/sql/user_compatibility_implementation.sql
Normal file
273
doc_mall/consumer/sql/user_compatibility_implementation.sql
Normal 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 $$;
|
||||
113
doc_mall/consumer/sql/validation_test.sql
Normal file
113
doc_mall/consumer/sql/validation_test.sql
Normal 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;
|
||||
113
doc_mall/consumer/sql/verify_mock_data_fix.sql
Normal file
113
doc_mall/consumer/sql/verify_mock_data_fix.sql
Normal 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 $$;
|
||||
Reference in New Issue
Block a user