实战教程:电商小程序
阅读前建议先了解 PG 模式概述,明确 PostgreSQL、身份认证、云存储和权限模型的关系。
本文以一个电商小程序为完整实战场景,带你端到端完成 CloudBase PostgreSQL 数据库下的数据建模、权限设计、REST API 调用的完整链路。覆盖的能力:
- 多表建模与外键约束
- 双层权限(GRANT + RLS)的综合运用
- 不同角色(
anon/authenticated/service_role)的差异化访问 - PostgREST REST API 的完整使用(筛选、排序、分页、Prefer)
- 基于
is_active的软下架、基于 JWT 的下单身份绑定、"订单不可改"等典型业务规则
💡 读完本文后,你就可以把同样的思路套用到博客、私人笔记、SaaS、社交等各种应用中。
场景概述
构建一个类似 Shopify / 有赞 / 小程序商城 的电商应用,需要:
| 功能 | 说明 |
|---|---|
| 🏪 商品浏览 | 所有人(包括未登录用户)可浏览上架商品 |
| 🔒 下架商品隐藏 | is_active=false 的商品对普通用户不可见 |
| 🛒 买家下单 | 登录用户可创建订单,买家身份自动绑定 |
| 👁️ 订单私有 | 每个买家只能看到自己的订单 |
| 🚫 订单不可改 | 买家下单后不能修改或取消,只有管理员可变更状态 |
| 👨💼 管理员全权 | 管理员可管理商品、处理所有订单状态流转 |
数据模型
┌────────────────────────┐ ┌─────────────────────────┐
│ products │ │ orders │
├────────────────────────┤ ├─────────────────────────┤
│ id (serial PK) │◄──┐ │ id (serial PK) │
│ name (text) │ │ │ product_id (int FK) │───┘
│ description (text) │ │ │ buyer_id (text, JWT) │
│ price (numeric) │ └──┤ quantity (int) │
│ stock (int) │ │ total_price (numeric) │
│ category (text) │ │ status (text) │
│ is_active (bool) │ │ address (jsonb) │
│ created_at (timestamptz)│ │ created_at (timestamptz)│
└────────────────────────┘ └─────────────────────────┘
前置准备
- 已 创建 PG 模式的云开发环境
- 已拿到 Publishable Key 和 API Key(详见 PG:身份认证 - 凭证与 JWT)
- 环境 ID 记为
<envId>,例如pg-test-3gxmdbdb580ecfd1 - API 端点:
- REST:
https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/v1 - Auth:
https://<envId>.api.tcloudbasegateway.com/auth/v1
- REST:
SQL 执行顺序
以下 SQL 严格按以下顺序执行,否则会因依赖关系报错:
建表 (CREATE TABLE)
→ 启用 RLS (ALTER TABLE ... ENABLE ROW LEVEL SECURITY)
→ 授权 (GRANT)
→ 创建策略 (CREATE POLICY)
清理时需反序:先删 Policy → 再解除 RLS → 最后删表(DROP TABLE ... CASCADE 可自动清理 Policy)。
🔧 SQL 可通过控制台的 SQL 编辑器 直接逐条执行,也可通过云 API
ExecutePGSql自动化部署(部分 DDL 在 API 路径下需用DO LANGUAGE plpgsql $$ BEGIN EXECUTE '...'; END $$包装重试;这是匿名过程代码块,详见 架构与权限模型 - ExecutePGSql)。
第一步:建表
-- ═══════════════════════════════════════════════════
-- 商品表
-- ═══════════════════════════════════════════════════
CREATE TABLE public.products (
id serial PRIMARY KEY,
name text NOT NULL,
description text,
price numeric(10,2) NOT NULL,
stock int DEFAULT 0,
category text,
is_active boolean DEFAULT true, -- true=上架, false=下架
created_at timestamptz DEFAULT now()
);
-- ═══════════════════════════════════════════════ ════
-- 订单表
-- ═══════════════════════════════════════════════════
CREATE TABLE public.orders (
id serial PRIMARY KEY,
product_id int NOT NULL REFERENCES public.products(id),
-- ⭐ buyer_id 自动从 JWT 中获取当前用户的 sub(auth.users.id 类型为 varchar(64))
buyer_id varchar(64) NOT NULL
DEFAULT (current_setting('request.jwt.claims', true)::json->>'sub'),
quantity int NOT NULL DEFAULT 1,
total_price numeric(10,2) NOT NULL,
status text NOT NULL DEFAULT 'pending', -- pending → paid → shipped → completed
address jsonb, -- 收货地址(结构化 JSON)
created_at timestamptz DEFAULT now(),
updated_at timestamptz DEFAULT now()
);
CREATE INDEX idx_orders_buyer_id ON public.orders(buyer_id);
关键设计:
buyer_id使用DEFAULT (current_setting('request.jwt.claims', true)::json->>'sub')自动从 JWT 中获取当前用户 ID,无需前端传入,杜绝了身份伪造的可能。
第二步:启用 RLS
ALTER TABLE public.products ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
启用 RLS 后,如果没有任何 Policy,所有非
service_role用户都无法访问任何数据(默认拒绝)。继续下一步。