304 lines
15 KiB
PL/PgSQL
304 lines
15 KiB
PL/PgSQL
-- ============================================
|
||
-- 数据分析实时大屏 - 基础业务表结构(创建版 / Create-only)
|
||
-- ============================================
|
||
-- 用途:创建业务核心表(orders, users, user_sessions, products, merchants 等)
|
||
-- 特点:
|
||
-- 1. 不做 DROP/DELETE/TRUNCATE(不清空数据)
|
||
-- 2. 通过 IF NOT EXISTS + 系统表判断,实现可重复执行
|
||
-- 3. 与 analytics_* 表(ANALYTICS_DB_SCHEMA.sql)配套使用
|
||
-- ============================================
|
||
|
||
|
||
-- ============================================
|
||
-- 1. 表结构创建
|
||
-- ============================================
|
||
|
||
-- 1.1 商家表
|
||
CREATE TABLE IF NOT EXISTS merchants (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
shop_name VARCHAR(255) NOT NULL,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.merchants IS '商家表';
|
||
COMMENT ON COLUMN public.merchants.id IS '商家ID';
|
||
COMMENT ON COLUMN public.merchants.shop_name IS '店铺名称';
|
||
COMMENT ON COLUMN public.merchants.created_at IS '创建时间';
|
||
|
||
-- 1.2 商品表
|
||
CREATE TABLE IF NOT EXISTS products (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
merchant_id UUID REFERENCES merchants(id) ON DELETE SET NULL,
|
||
name VARCHAR(255) NOT NULL,
|
||
price DECIMAL(10, 2) NOT NULL,
|
||
sales INTEGER DEFAULT 0,
|
||
status INTEGER DEFAULT 1,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.products IS '商品表';
|
||
COMMENT ON COLUMN public.products.id IS '商品ID';
|
||
COMMENT ON COLUMN public.products.merchant_id IS '所属商家ID';
|
||
COMMENT ON COLUMN public.products.name IS '商品名称';
|
||
COMMENT ON COLUMN public.products.price IS '价格';
|
||
COMMENT ON COLUMN public.products.sales IS '销量';
|
||
COMMENT ON COLUMN public.products.status IS '状态(1:上架, 0:下架)';
|
||
COMMENT ON COLUMN public.products.created_at IS '创建时间';
|
||
|
||
-- 1.3 用户(统计兼容)表
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id UUID PRIMARY KEY,
|
||
phone VARCHAR(20) UNIQUE,
|
||
email VARCHAR(255),
|
||
nickname VARCHAR(100),
|
||
last_login_at TIMESTAMP WITH TIME ZONE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.users IS '用户(统计兼容表,不作为权威用户表)';
|
||
COMMENT ON COLUMN public.users.id IS '用户ID(建议与 auth.users.id/ak_users.id 对齐)';
|
||
COMMENT ON COLUMN public.users.phone IS '手机号(可选)';
|
||
COMMENT ON COLUMN public.users.email IS '邮箱(可选)';
|
||
COMMENT ON COLUMN public.users.nickname IS '昵称';
|
||
COMMENT ON COLUMN public.users.last_login_at IS '最后登录时间';
|
||
COMMENT ON COLUMN public.users.created_at IS '创建时间';
|
||
COMMENT ON COLUMN public.users.updated_at IS '更新时间';
|
||
|
||
-- 1.4 订单表
|
||
CREATE TABLE IF NOT EXISTS orders (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
merchant_id UUID REFERENCES merchants(id) ON DELETE SET NULL,
|
||
total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||
status INTEGER NOT NULL DEFAULT 0,
|
||
payment_method VARCHAR(50),
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.orders IS '订单表';
|
||
COMMENT ON COLUMN public.orders.id IS '订单ID';
|
||
COMMENT ON COLUMN public.orders.user_id IS '用户ID';
|
||
COMMENT ON COLUMN public.orders.merchant_id IS '商家ID';
|
||
COMMENT ON COLUMN public.orders.total_amount IS '订单总金额';
|
||
COMMENT ON COLUMN public.orders.status IS '订单状态(0:待支付, 1:已支付, 2:已完成, 3:已取消)';
|
||
COMMENT ON COLUMN public.orders.payment_method IS '支付方式';
|
||
COMMENT ON COLUMN public.orders.created_at IS '创建时间';
|
||
COMMENT ON COLUMN public.orders.updated_at IS '更新时间';
|
||
|
||
-- 1.5 订单商品关联表
|
||
CREATE TABLE IF NOT EXISTS order_items (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||
product_id UUID NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
|
||
quantity INTEGER NOT NULL DEFAULT 1,
|
||
price DECIMAL(10, 2) NOT NULL,
|
||
total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.order_items IS '订单商品关联表';
|
||
COMMENT ON COLUMN public.order_items.id IS '主键';
|
||
COMMENT ON COLUMN public.order_items.order_id IS '订单ID';
|
||
COMMENT ON COLUMN public.order_items.product_id IS '商品ID';
|
||
COMMENT ON COLUMN public.order_items.quantity IS '数量';
|
||
COMMENT ON COLUMN public.order_items.price IS '单价';
|
||
COMMENT ON COLUMN public.order_items.total_amount IS '总价';
|
||
COMMENT ON COLUMN public.order_items.created_at IS '创建时间';
|
||
|
||
-- 1.6 用户会话表
|
||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
session_token VARCHAR(255) UNIQUE,
|
||
last_active_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
is_active BOOLEAN DEFAULT true,
|
||
ip_address VARCHAR(45),
|
||
user_agent TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.user_sessions IS '用户会话表(用于在线用户统计)';
|
||
COMMENT ON COLUMN public.user_sessions.id IS '会话ID';
|
||
COMMENT ON COLUMN public.user_sessions.user_id IS '用户ID';
|
||
COMMENT ON COLUMN public.user_sessions.session_token IS '会话Token';
|
||
COMMENT ON COLUMN public.user_sessions.last_active_at IS '最后活跃时间';
|
||
COMMENT ON COLUMN public.user_sessions.is_active IS '是否活跃';
|
||
COMMENT ON COLUMN public.user_sessions.ip_address IS 'IP地址';
|
||
COMMENT ON COLUMN public.user_sessions.user_agent IS '用户代理';
|
||
COMMENT ON COLUMN public.user_sessions.created_at IS '创建时间';
|
||
COMMENT ON COLUMN public.user_sessions.updated_at IS '更新时间';
|
||
|
||
-- 1.7 访问日志表
|
||
CREATE TABLE IF NOT EXISTS page_views (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||
path VARCHAR(255),
|
||
source VARCHAR(50) DEFAULT 'direct',
|
||
referrer VARCHAR(255),
|
||
ip_address VARCHAR(45),
|
||
user_agent TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
);
|
||
COMMENT ON TABLE public.page_views IS '访问日志表(用于转化率分析)';
|
||
COMMENT ON COLUMN public.page_views.id IS '主键';
|
||
COMMENT ON COLUMN public.page_views.user_id IS '用户ID(可空,表示匿名访问)';
|
||
COMMENT ON COLUMN public.page_views.path IS '访问路径';
|
||
COMMENT ON COLUMN public.page_views.source IS '流量来源(direct/search/social/ad)';
|
||
COMMENT ON COLUMN public.page_views.referrer IS '来源页面';
|
||
COMMENT ON COLUMN public.page_views.ip_address IS 'IP地址';
|
||
COMMENT ON COLUMN public.page_views.user_agent IS '用户代理';
|
||
COMMENT ON COLUMN public.page_views.created_at IS '创建时间';
|
||
|
||
-- ============================================
|
||
-- 2. 索引创建
|
||
-- ============================================
|
||
|
||
-- orders
|
||
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
|
||
CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_orders_created_at_status ON orders(created_at, status);
|
||
|
||
-- user_sessions
|
||
CREATE INDEX IF NOT EXISTS idx_user_sessions_last_active_at ON user_sessions(last_active_at);
|
||
CREATE INDEX IF NOT EXISTS idx_user_sessions_is_active ON user_sessions(is_active);
|
||
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_user_sessions_created_at ON user_sessions(created_at);
|
||
|
||
-- users
|
||
CREATE INDEX IF NOT EXISTS idx_users_last_login_at ON users(last_login_at);
|
||
|
||
-- order_items
|
||
CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id);
|
||
CREATE INDEX IF NOT EXISTS idx_order_items_product_id ON order_items(product_id);
|
||
|
||
-- page_views
|
||
CREATE INDEX IF NOT EXISTS idx_page_views_user_id ON page_views(user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_page_views_created_at ON page_views(created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_page_views_source ON page_views(source);
|
||
|
||
-- ============================================
|
||
-- 3. 触发器函数和触发器
|
||
-- ============================================
|
||
|
||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||
RETURNS TRIGGER AS $$
|
||
BEGIN
|
||
NEW.updated_at = NOW();
|
||
RETURN NEW;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
|
||
-- 为需要 updated_at 的表添加触发器
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_orders_updated_at') THEN
|
||
EXECUTE 'CREATE TRIGGER update_orders_updated_at BEFORE UPDATE ON public.orders FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column()';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_user_sessions_updated_at') THEN
|
||
EXECUTE 'CREATE TRIGGER update_user_sessions_updated_at BEFORE UPDATE ON public.user_sessions FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column()';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_users_updated_at') THEN
|
||
EXECUTE 'CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column()';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- ============================================
|
||
-- 4. 行级安全策略(RLS)
|
||
-- ============================================
|
||
|
||
-- 启用 RLS
|
||
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE user_sessions ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE merchants ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE order_items ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE page_views ENABLE ROW LEVEL SECURITY;
|
||
|
||
-- orders: 用户只能查看和管理自己的订单
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='orders' AND policyname='Users can view own orders') THEN
|
||
EXECUTE 'CREATE POLICY "Users can view own orders" ON public.orders FOR SELECT USING (auth.uid() = user_id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='orders' AND policyname='Users can insert own orders') THEN
|
||
EXECUTE 'CREATE POLICY "Users can insert own orders" ON public.orders FOR INSERT WITH CHECK (auth.uid() = user_id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='orders' AND policyname='Users can update own orders') THEN
|
||
EXECUTE 'CREATE POLICY "Users can update own orders" ON public.orders FOR UPDATE USING (auth.uid() = user_id)';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- order_items: 用户只能查看自己订单的商品
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='order_items' AND policyname='Users can view own order items') THEN
|
||
EXECUTE 'CREATE POLICY "Users can view own order items" ON public.order_items FOR SELECT USING (EXISTS (SELECT 1 FROM public.orders WHERE public.orders.id = public.order_items.order_id AND public.orders.user_id = auth.uid()))';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='order_items' AND policyname='Users can insert own order items') THEN
|
||
EXECUTE 'CREATE POLICY "Users can insert own order items" ON public.order_items FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM public.orders WHERE public.orders.id = public.order_items.order_id AND public.orders.user_id = auth.uid()))';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- user_sessions: 用户只能查看和管理自己的会话
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='user_sessions' AND policyname='Users can view own sessions') THEN
|
||
EXECUTE 'CREATE POLICY "Users can view own sessions" ON public.user_sessions FOR SELECT USING (auth.uid() = user_id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='user_sessions' AND policyname='Users can insert own sessions') THEN
|
||
EXECUTE 'CREATE POLICY "Users can insert own sessions" ON public.user_sessions FOR INSERT WITH CHECK (auth.uid() = user_id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='user_sessions' AND policyname='Users can update own sessions') THEN
|
||
EXECUTE 'CREATE POLICY "Users can update own sessions" ON public.user_sessions FOR UPDATE USING (auth.uid() = user_id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='user_sessions' AND policyname='Users can delete own sessions') THEN
|
||
EXECUTE 'CREATE POLICY "Users can delete own sessions" ON public.user_sessions FOR DELETE USING (auth.uid() = user_id)';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- users: 用户只能查看和管理自己的记录
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='users' AND policyname='Users can view own profile') THEN
|
||
EXECUTE 'CREATE POLICY "Users can view own profile" ON public.users FOR SELECT USING (auth.uid() = id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='users' AND policyname='Users can insert own profile') THEN
|
||
EXECUTE 'CREATE POLICY "Users can insert own profile" ON public.users FOR INSERT WITH CHECK (auth.uid() = id)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='users' AND policyname='Users can update own profile') THEN
|
||
EXECUTE 'CREATE POLICY "Users can update own profile" ON public.users FOR UPDATE USING (auth.uid() = id)';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- products: 任何人可读,认证用户可管理(简化策略)
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='products' AND policyname='Anyone can view products') THEN
|
||
EXECUTE 'CREATE POLICY "Anyone can view products" ON public.products FOR SELECT USING (true)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='products' AND policyname='Authenticated can manage products') THEN
|
||
EXECUTE 'CREATE POLICY "Authenticated can manage products" ON public.products FOR ALL USING (auth.role() = ''authenticated'')';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- merchants: 任何人可读,认证用户可管理(简化策略)
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='merchants' AND policyname='Anyone can view merchants') THEN
|
||
EXECUTE 'CREATE POLICY "Anyone can view merchants" ON public.merchants FOR SELECT USING (true)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='merchants' AND policyname='Authenticated can manage merchants') THEN
|
||
EXECUTE 'CREATE POLICY "Authenticated can manage merchants" ON public.merchants FOR ALL USING (auth.role() = ''authenticated'')';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- page_views: 任何人可插入,用户只能读自己的记录
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='page_views' AND policyname='Anyone can insert page views') THEN
|
||
EXECUTE 'CREATE POLICY "Anyone can insert page views" ON public.page_views FOR INSERT WITH CHECK (true)';
|
||
END IF;
|
||
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename='page_views' AND policyname='Users can view own page views') THEN
|
||
EXECUTE 'CREATE POLICY "Users can view own page views" ON public.page_views FOR SELECT USING (auth.uid() = user_id)';
|
||
END IF;
|
||
END $$;
|