41 KiB
41 KiB
🔨 doc_mall 模块技术实现拆解
📋 目录
一、整体架构
1.1 技术栈架构图
┌─────────────────────────────────────────────────────────┐
│ 前端层 (uni-app-x) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 消费者端 │ │ 商家端 │ │ 配送端 │ │ 管理端 │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴────────────┘ │
│ │ │
│ ┌───────────▼───────────┐ │
│ │ Supabase 客户端封装 │ │
│ │ (AkSupa.uts) │ │
│ └───────────┬───────────┘ │
└──────────────────────────┼──────────────────────────────┘
│ HTTPS REST API
┌───────────────────────────▼──────────────────────────────┐
│ API 层 (PostgREST) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 自动生成 REST API │ │
│ │ - GET /rest/v1/ml_products │ │
│ │ - POST /rest/v1/ml_orders │ │
│ │ - RPC /rest/v1/rpc/calculate_cart_total │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────┬──────────────────────────────┘
│
┌───────────────────────────▼──────────────────────────────┐
│ 认证层 (Supabase Auth) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ JWT Token 认证 │ │
│ │ - 用户登录/注册 │ │
│ │ - Token 刷新 │ │
│ │ - 权限验证 │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────┬──────────────────────────────┘
│
┌───────────────────────────▼──────────────────────────────┐
│ 权限层 (RLS - Row Level Security) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 行级安全策略 │ │
│ │ - 用户数据隔离 │ │
│ │ - 商家权限控制 │ │
│ │ - 公开数据访问 │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────┬──────────────────────────────┘
│
┌───────────────────────────▼──────────────────────────────┐
│ 数据库层 (PostgreSQL) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 表结构 │ │ 触发器 │ │ 函数 │ │ 视图 │ │
│ │ 索引 │ │ RLS策略 │ │ 序列 │ │ 扩展 │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 核心设计模式
BaaS (Backend as a Service) 模式
- 特点: 使用 Supabase 作为后端服务,无需自建后端服务器
- 优势:
- 自动生成 REST API
- 内置认证和权限系统
- 实时数据同步
- 减少后端开发工作量
数据库优先 (Database-First) 模式
- 流程: 数据库设计 → 自动生成 API → 前端调用
- 实现:
- 设计数据库表结构
- PostgREST 自动生成 REST API
- 前端通过 Supabase 客户端调用
类型驱动开发 (Type-Driven Development)
- 实现: TypeScript/UTS 类型定义
- 文件:
types/mall-types.uts - 优势: 类型安全、代码提示、减少错误
二、数据库层实现
2.1 表结构设计
2.1.1 核心表结构
用户扩展表 (ml_user_profiles)
CREATE TABLE 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,
status INTEGER DEFAULT 1, -- 1:正常 2:冻结 3:注销 4:待审核
real_name VARCHAR(100), -- 真实姓名
id_card VARCHAR(32), -- 身份证号
credit_score INTEGER DEFAULT 100, -- 信用分数 0-1000
verification_status INTEGER DEFAULT 0, -- 认证状态
verification_data JSONB DEFAULT '{}', -- 认证相关数据
preferences JSONB DEFAULT '{}', -- 用户偏好设置
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
商品表 (ml_products)
CREATE TABLE 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,
description TEXT,
main_image_url TEXT,
image_urls JSONB DEFAULT '[]', -- 商品图片数组
base_price DECIMAL(12,2) NOT NULL,
total_stock INTEGER DEFAULT 0,
available_stock INTEGER DEFAULT 0,
status INTEGER DEFAULT 1, -- 1:上架 2:下架 3:草稿 4:删除
view_count INTEGER DEFAULT 0,
sale_count INTEGER DEFAULT 0,
rating_avg DECIMAL(3,2) DEFAULT 0.00,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
订单表 (ml_orders)
CREATE TABLE 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,
shipping_address JSONB NOT NULL, -- 收货地址JSON
order_status INTEGER DEFAULT 1, -- 1:待支付 2:待发货 3:待收货 4:已完成
payment_status INTEGER DEFAULT 1, -- 1:未支付 2:已支付 3:部分退款 4:全额退款
shipping_status INTEGER DEFAULT 1, -- 1:未发货 2:已发货 3:运输中 4:已送达
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
2.2 索引设计
2.2.1 性能优化索引
-- 商品表索引
CREATE INDEX idx_ml_products_merchant ON public.ml_products(merchant_id);
CREATE INDEX idx_ml_products_category ON public.ml_products(category_id);
CREATE INDEX idx_ml_products_status ON public.ml_products(status);
CREATE INDEX idx_ml_products_cid ON public.ml_products(cid); -- SEO查询
CREATE INDEX idx_ml_products_created ON public.ml_products(created_at DESC);
-- 订单表索引
CREATE INDEX idx_ml_orders_user ON public.ml_orders(user_id);
CREATE INDEX idx_ml_orders_merchant ON public.ml_orders(merchant_id);
CREATE INDEX idx_ml_orders_status ON public.ml_orders(order_status);
CREATE INDEX idx_ml_orders_created ON public.ml_orders(created_at DESC);
-- JSONB GIN索引(用于JSON字段查询)
CREATE INDEX idx_ml_products_images_gin ON public.ml_products USING GIN(image_urls);
CREATE INDEX idx_ml_orders_address_gin ON public.ml_orders USING GIN(shipping_address);
2.3 触发器实现
2.3.1 自动更新时间戳
-- 触发器函数
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 应用到表
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();
2.3.2 确保唯一默认地址
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 TRIGGER trigger_ensure_single_default_address
BEFORE INSERT OR UPDATE ON public.ml_user_addresses
FOR EACH ROW EXECUTE FUNCTION public.ensure_single_default_address();
2.3.3 自动更新商品库存
CREATE OR REPLACE FUNCTION public.update_product_stock()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
-- 插入订单商品时,减少库存
UPDATE public.ml_products
SET available_stock = available_stock - NEW.quantity
WHERE id = NEW.product_id;
ELSIF TG_OP = 'DELETE' THEN
-- 删除订单商品时,恢复库存
UPDATE public.ml_products
SET available_stock = available_stock + OLD.quantity
WHERE id = OLD.product_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_product_stock
AFTER INSERT OR DELETE ON public.ml_order_items
FOR EACH ROW EXECUTE FUNCTION public.update_product_stock();
2.4 数据库函数实现
2.4.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.calculate_cart_total(p_user_id UUID)
RETURNS DECIMAL AS $$
DECLARE
total_amount DECIMAL(12,2) := 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;
获取用户默认地址
CREATE OR REPLACE FUNCTION public.get_user_default_address(p_user_id UUID)
RETURNS TABLE (
id UUID,
receiver_name VARCHAR,
receiver_phone VARCHAR,
address_detail TEXT
) AS $$
BEGIN
RETURN QUERY
SELECT
a.id,
a.receiver_name,
a.receiver_phone,
a.address_detail
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;
2.5 RLS 行级安全策略
2.5.1 用户数据隔离
-- 用户只能访问自己的数据
CREATE POLICY ml_user_profiles_select_policy ON public.ml_user_profiles
FOR SELECT USING (
auth.uid()::text = (SELECT auth_id::text 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()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id)
);
2.5.2 商品权限控制
-- 所有人可查看已上架商品
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()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = merchant_id)
);
CREATE POLICY ml_products_update_policy ON public.ml_products
FOR UPDATE USING (
auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = merchant_id)
);
2.5.3 订单权限控制
-- 用户和商家都可查看相关订单
CREATE POLICY ml_orders_select_policy ON public.ml_orders
FOR SELECT USING (
auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id)
OR
auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = merchant_id)
);
-- 只有用户能创建订单
CREATE POLICY ml_orders_insert_policy ON public.ml_orders
FOR INSERT WITH CHECK (
auth.uid()::text = (SELECT auth_id::text FROM public.ak_users WHERE id = user_id)
);
三、后端/API层实现
3.1 PostgREST 自动生成 API
3.1.1 REST API 自动生成机制
Supabase 基于 PostgREST 自动为每个表生成 REST API:
查询 API
GET /rest/v1/ml_products
GET /rest/v1/ml_products?id=eq.{uuid}
GET /rest/v1/ml_products?status=eq.1&limit=20
GET /rest/v1/ml_products?base_price=gte.100&base_price=lte.500
插入 API
POST /rest/v1/ml_orders
Content-Type: application/json
{
"user_id": "uuid",
"merchant_id": "uuid",
"total_amount": 100.00
}
更新 API
PATCH /rest/v1/ml_products?id=eq.{uuid}
Content-Type: application/json
{
"status": 2
}
删除 API
DELETE /rest/v1/ml_user_favorites?id=eq.{uuid}
RPC 函数调用
POST /rest/v1/rpc/calculate_cart_total
Content-Type: application/json
{
"p_user_id": "uuid"
}
3.2 查询操作符支持
PostgREST 支持丰富的查询操作符:
// 等于
?status=eq.1
// 不等于
?status=neq.2
// 大于/小于
?base_price=gte.100&base_price=lte.500
// 模糊查询
?name=ilike.%商品%
// IN 查询
?category_id=in.(id1,id2,id3)
// IS NULL
?deleted_at=is.null
// JSONB 查询
?preferences->theme=eq.dark
3.3 分页和排序
// 分页
?limit=20&offset=0
// 或使用 Range 头
Range: 0-19
// 排序
?order=created_at.desc
?order=base_price.asc,created_at.desc
// 计数
Prefer: count=exact
四、前端实现
4.1 项目结构
pages/mall/
├── consumer/ # 消费者端
│ ├── index.uvue # 首页
│ ├── product-detail.uvue # 商品详情
│ ├── cart.uvue # 购物车
│ ├── checkout.uvue # 结算页
│ ├── orders.uvue # 订单列表
│ └── subscription/ # 订阅相关
├── merchant/ # 商家端
│ ├── index.uvue # 商家首页
│ └── product-detail.uvue
├── delivery/ # 配送端
├── admin/ # 管理端
└── service/ # 客服端
components/supadb/
├── aksupa.uts # Supabase 客户端封装
├── aksupainstance.uts # 全局单例
└── aksuparealtime.uts # 实时订阅
types/
└── mall-types.uts # 类型定义
4.2 Supabase 客户端封装
4.2.1 客户端初始化
文件: components/supadb/aksupainstance.uts
import AkSupa from './aksupa.uts'
import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
// 创建全局 Supabase 客户端实例
const supa = new AkSupa(SUPA_URL, SUPA_KEY)
// 自动登录 (开发环境)
const supaReady: Promise<boolean> = (async () => {
try {
await supa.signIn('test@example.com', 'password')
return true
} catch (err) {
console.error('Supabase auto sign-in failed', err)
return false
}
})()
export { supaReady }
export default supa
4.2.2 API 调用封装
文件: components/supadb/aksupa.uts
export class AkSupa {
baseUrl: string
apikey: string
constructor(baseUrl: string, apikey: string) {
this.baseUrl = baseUrl
this.apikey = apikey
}
// 查询数据
async select(
table: string,
filter?: string | null,
options?: AkSupaSelectOptions
): Promise<AkReqResponse<any>> {
let url = this.baseUrl + '/rest/v1/' + table
let headers = {
apikey: this.apikey,
'Content-Type': 'application/json',
Authorization: `Bearer ${AkReq.getToken() ?? ''}`
} as UTSJSONObject
// 构建查询参数
let params: string[] = []
if (options?.limit) params.push(`limit=${options.limit}`)
if (options?.order) params.push(`order=${encodeURIComponent(options.order)}`)
if (filter) params.push(filter)
if (params.length > 0) {
url += '?' + params.join('&')
}
return await this.requestWithAutoRefresh({
url,
method: 'GET',
headers
})
}
// 插入数据
async insert(
table: string,
row: UTSJSONObject | Array<UTSJSONObject>
): Promise<AkReqResponse<any>> {
const url = this.baseUrl + '/rest/v1/' + table
const headers = {
apikey: this.apikey,
'Content-Type': 'application/json',
Authorization: `Bearer ${AkReq.getToken() ?? ''}`,
Prefer: 'return=representation'
} as UTSJSONObject
return await this.requestWithAutoRefresh({
url,
method: 'POST',
headers,
data: row
})
}
// 更新数据
async update(
table: string,
filter: UTSJSONObject,
data: UTSJSONObject
): Promise<AkReqResponse<any>> {
const filterStr = buildSupabaseFilterQuery(filter)
const url = this.baseUrl + '/rest/v1/' + table + '?' + filterStr
const headers = {
apikey: this.apikey,
'Content-Type': 'application/json',
Authorization: `Bearer ${AkReq.getToken() ?? ''}`,
Prefer: 'return=representation'
} as UTSJSONObject
return await this.requestWithAutoRefresh({
url,
method: 'PATCH',
headers,
data
})
}
// 调用 RPC 函数
async rpc(
functionName: string,
params: UTSJSONObject
): Promise<AkReqResponse<any>> {
const url = `${this.baseUrl}/rest/v1/rpc/${functionName}`
const headers = {
apikey: this.apikey,
'Content-Type': 'application/json',
Authorization: `Bearer ${AkReq.getToken() ?? ''}`
} as UTSJSONObject
return await this.requestWithAutoRefresh({
url,
method: 'POST',
headers,
data: params
})
}
}
4.3 页面实现示例
4.3.1 商品列表页面
文件: pages/mall/consumer/index.uvue
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import type { ProductType } from '@/types/mall-types.uts'
import supa from '@/components/supadb/aksupainstance.uts'
const productList = ref<Array<ProductType>>([])
const isLoading = ref<boolean>(false)
const page = ref<number>(1)
const pageSize = ref<number>(20)
// 加载商品列表
const loadProducts = async (loadMore: boolean = false) => {
if (isLoading.value) return
isLoading.value = true
try {
const currentPage = loadMore ? page.value + 1 : 1
// 使用链式查询构建器
const res = await supa
.from('ml_products')
.select('*')
.eq('status', 1)
.order('created_at', { ascending: false })
.range((currentPage - 1) * pageSize.value, currentPage * pageSize.value - 1)
if (res.success && res.data) {
const newProducts = res.data as Array<ProductType>
if (loadMore) {
productList.value.push(...newProducts)
} else {
productList.value = newProducts
}
page.value = currentPage
}
} catch (err) {
console.error('加载商品失败:', err)
} finally {
isLoading.value = false
}
}
onMounted(() => {
loadProducts()
})
</script>
4.3.2 商品详情页面
// 加载商品详情
const loadProductDetail = async (productId: string) => {
try {
// 查询商品信息
const productRes = await supa.select('ml_products',
`id=eq.${productId}`,
{ single: true }
)
if (productRes.success && productRes.data) {
product.value = productRes.data as ProductType
}
// 查询商品SKU
const skuRes = await supa.select('ml_product_skus', {
product_id: productId
})
if (skuRes.success && skuRes.data) {
productSkus.value = skuRes.data as Array<ProductSkuType>
}
// 查询商家信息
const merchantRes = await supa.select('ml_shops', {
merchant_id: product.value.merchant_id
}, { single: true })
if (merchantRes.success && merchantRes.data) {
merchant.value = merchantRes.data as MerchantType
}
} catch (err) {
console.error('加载商品详情失败:', err)
}
}
4.3.3 购物车操作
// 添加到购物车
const addToCart = async (product: ProductType, skuId?: string, quantity: number = 1) => {
try {
const userId = getCurrentUserId()
if (!userId) {
uni.showToast({ title: '请先登录', icon: 'error' })
return
}
// 检查购物车中是否已存在
const existingRes = await supa.select('ml_shopping_cart', {
user_id: userId,
product_id: product.id,
sku_id: skuId || null
}, { single: true })
if (existingRes.success && existingRes.data) {
// 更新数量
await supa.update('ml_shopping_cart',
{ id: existingRes.data.id },
{ quantity: existingRes.data.quantity + quantity }
)
} else {
// 新增购物车项
await supa.insert('ml_shopping_cart', {
user_id: userId,
product_id: product.id,
sku_id: skuId || null,
quantity: quantity,
selected: true
})
}
uni.showToast({ title: '已添加到购物车', icon: 'success' })
loadCartCount()
} catch (err) {
console.error('添加到购物车失败:', err)
uni.showToast({ title: '操作失败', icon: 'error' })
}
}
4.3.4 创建订单
// 创建订单
const createOrder = async (cartItems: Array<CartItemType>, address: UserAddressType) => {
try {
// 1. 计算订单总金额(使用数据库函数)
const totalRes = await supa.rpc('calculate_cart_total', {
p_user_id: getCurrentUserId()
})
if (!totalRes.success) {
throw new Error('计算订单金额失败')
}
const totalAmount = totalRes.data as number
// 2. 生成订单号
const orderNoRes = await supa.rpc('generate_order_no')
if (!orderNoRes.success) {
throw new Error('生成订单号失败')
}
const orderNo = orderNoRes.data as string
// 3. 创建订单
const orderRes = await supa.insert('ml_orders', {
order_no: orderNo,
user_id: getCurrentUserId(),
merchant_id: cartItems[0].product.merchant_id,
product_amount: totalAmount,
discount_amount: 0,
shipping_fee: 0,
total_amount: totalAmount,
shipping_address: {
receiver_name: address.receiver_name,
receiver_phone: address.receiver_phone,
province: address.province,
city: address.city,
district: address.district,
address_detail: address.address_detail
},
order_status: 1, // 待支付
payment_status: 1,
shipping_status: 1
})
if (!orderRes.success) {
throw new Error('创建订单失败')
}
const orderId = orderRes.data.id
// 4. 创建订单商品项
const orderItems = cartItems.map(item => ({
order_id: orderId,
product_id: item.product_id,
sku_id: item.sku_id || null,
product_name: item.product.name,
price: item.sku?.price || item.product.price,
quantity: item.quantity,
total_amount: (item.sku?.price || item.product.price) * item.quantity
}))
await supa.insert('ml_order_items', orderItems)
// 5. 清空购物车
for (const item of cartItems) {
await supa.delete('ml_shopping_cart', { id: item.id })
}
uni.showToast({ title: '订单创建成功', icon: 'success' })
// 跳转到订单详情
uni.navigateTo({
url: `/pages/mall/consumer/order-detail?orderId=${orderId}`
})
} catch (err) {
console.error('创建订单失败:', err)
uni.showToast({ title: '创建订单失败', icon: 'error' })
}
}
4.4 实时数据同步
4.4.1 订阅订单状态更新
import { AkSupaRealtime } from '@/components/supadb/aksuparealtime.uts'
// 订阅订单状态更新
const subscribeOrderStatus = (orderId: string, callback: (payload: any) => void) => {
const realtime = new AkSupaRealtime(WS_URL, SUPA_KEY)
realtime.subscribe('ml_orders', {
filter: `id=eq.${orderId}`,
event: 'UPDATE',
callback: (payload) => {
console.log('订单状态更新:', payload)
callback(payload)
}
})
}
// 使用
onMounted(() => {
subscribeOrderStatus(orderId, (payload) => {
// 更新订单状态
order.value.order_status = payload.new.order_status
})
})
五、数据流机制
5.1 数据流向图
用户操作
│
▼
前端页面 (uni-app-x)
│
▼
Supabase 客户端 (AkSupa)
│
▼ HTTP Request
│
├─ Headers: apikey, Authorization (JWT Token)
│
▼
PostgREST API
│
├─ 解析请求
├─ 验证 JWT Token
├─ 应用 RLS 策略
│
▼
PostgreSQL 数据库
│
├─ 执行 SQL 查询
├─ 触发触发器
├─ 执行函数
│
▼
返回数据
│
▼
PostgREST 格式化响应
│
▼ HTTP Response
│
├─ JSON 数据
├─ 状态码
│
▼
Supabase 客户端处理
│
├─ 解析响应
├─ 错误处理
│
▼
前端页面更新 UI
5.2 认证流程
1. 用户登录
│
▼
2. Supabase Auth 验证
│
├─ 验证邮箱/密码
├─ 生成 JWT Token
│
▼
3. 存储 Token
│
├─ 本地存储 (uni.setStorageSync)
│
▼
4. 后续请求自动携带 Token
│
├─ Authorization: Bearer <token>
│
▼
5. PostgREST 验证 Token
│
├─ 解析 JWT
├─ 获取 auth.uid()
│
▼
6. RLS 策略应用
│
├─ 根据 auth.uid() 过滤数据
5.3 权限控制流程
请求到达 PostgREST
│
▼
解析 JWT Token
│
├─ 获取 auth.uid()
│
▼
查找 RLS 策略
│
├─ SELECT 策略 → USING 子句
├─ INSERT 策略 → WITH CHECK 子句
├─ UPDATE 策略 → USING + WITH CHECK
├─ DELETE 策略 → USING 子句
│
▼
应用策略条件
│
├─ 用户只能访问自己的数据
├─ 商家只能管理自己的商品
├─ 公开数据所有人可查看
│
▼
执行 SQL 查询
│
├─ 自动添加 WHERE 条件
│
▼
返回过滤后的数据
六、业务逻辑实现
6.1 商品管理流程
6.1.1 商品上架流程
// 商家上架商品
const publishProduct = async (productData: any) => {
// 1. 验证商家权限
const merchantRes = await supa.select('ml_user_profiles', {
user_id: getCurrentUserId(),
user_type: 2, // 商家
verification_status: 1 // 已认证
}, { single: true })
if (!merchantRes.success || !merchantRes.data) {
throw new Error('您还不是认证商家')
}
// 2. 创建商品
const productRes = await supa.insert('ml_products', {
merchant_id: getCurrentUserId(),
category_id: productData.category_id,
name: productData.name,
description: productData.description,
base_price: productData.price,
total_stock: productData.stock,
available_stock: productData.stock,
status: 1, // 上架
...productData
})
// 3. 创建商品SKU
if (productData.skus && productData.skus.length > 0) {
const skus = productData.skus.map((sku: any) => ({
product_id: productRes.data.id,
sku_code: sku.sku_code,
specifications: sku.specifications,
price: sku.price,
stock: sku.stock
}))
await supa.insert('ml_product_skus', skus)
}
return productRes.data
}
6.2 订单处理流程
6.2.1 订单状态流转
// 订单状态枚举
const ORDER_STATUS = {
PENDING_PAYMENT: 1, // 待支付
PAID: 2, // 已支付
SHIPPED: 3, // 已发货
DELIVERED: 4, // 已送达
COMPLETED: 5, // 已完成
CANCELLED: 6, // 已取消
REFUNDING: 7, // 退款中
REFUNDED: 8 // 已退款
}
// 更新订单状态
const updateOrderStatus = async (orderId: string, newStatus: number) => {
const updateData: any = {
order_status: newStatus,
updated_at: new Date().toISOString()
}
// 根据状态设置相应时间戳
switch (newStatus) {
case ORDER_STATUS.PAID:
updateData.paid_at = new Date().toISOString()
updateData.payment_status = 2 // 已支付
break
case ORDER_STATUS.SHIPPED:
updateData.shipped_at = new Date().toISOString()
updateData.shipping_status = 2 // 已发货
break
case ORDER_STATUS.DELIVERED:
updateData.delivered_at = new Date().toISOString()
updateData.shipping_status = 4 // 已送达
break
case ORDER_STATUS.COMPLETED:
updateData.completed_at = new Date().toISOString()
break
}
await supa.update('ml_orders', { id: orderId }, updateData)
}
6.3 库存管理机制
6.3.1 库存扣减流程
// 下单时扣减库存
const deductStock = async (orderItems: Array<any>) => {
for (const item of orderItems) {
if (item.sku_id) {
// 扣减 SKU 库存
const skuRes = await supa.select('ml_product_skus', {
id: item.sku_id
}, { single: true })
if (skuRes.data.stock < item.quantity) {
throw new Error(`商品 ${item.product_name} 库存不足`)
}
await supa.update('ml_product_skus',
{ id: item.sku_id },
{ stock: skuRes.data.stock - item.quantity }
)
} else {
// 扣减商品总库存
const productRes = await supa.select('ml_products', {
id: item.product_id
}, { single: true })
if (productRes.data.available_stock < item.quantity) {
throw new Error(`商品 ${item.product_name} 库存不足`)
}
await supa.update('ml_products',
{ id: item.product_id },
{
available_stock: productRes.data.available_stock - item.quantity,
sale_count: productRes.data.sale_count + item.quantity
}
)
}
}
}
七、安全机制
7.1 多层安全防护
┌─────────────────────────────────────┐
│ 1. 网络层安全 │
│ - HTTPS 加密传输 │
│ - SSL/TLS 证书 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. 认证层安全 │
│ - JWT Token 验证 │
│ - Token 过期机制 │
│ - 自动刷新 Token │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. 权限层安全 (RLS) │
│ - 行级安全策略 │
│ - 基于用户角色的权限控制 │
│ - 数据隔离 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 4. 应用层安全 │
│ - 前端数据验证 │
│ - 输入过滤和转义 │
│ - 防止 SQL 注入 │
└─────────────────────────────────────┘
7.2 数据验证机制
7.2.1 数据库约束
-- CHECK 约束
CONSTRAINT chk_ml_product_status CHECK (status IN (1,2,3,4))
CONSTRAINT chk_ml_order_status CHECK (order_status IN (1,2,3,4,5,6,7))
CONSTRAINT chk_ml_credit_score CHECK (credit_score >= 0 AND credit_score <= 1000)
-- 外键约束
REFERENCES public.ak_users(id) ON DELETE CASCADE
REFERENCES public.ml_products(id) ON DELETE CASCADE
-- 唯一约束
UNIQUE(product_code)
UNIQUE(order_no)
UNIQUE(user_id, product_id, sku_id) -- 购物车唯一性
7.2.2 前端验证
// 表单验证
const validateOrderData = (orderData: any): boolean => {
if (!orderData.user_id) {
uni.showToast({ title: '用户ID不能为空', icon: 'error' })
return false
}
if (!orderData.total_amount || orderData.total_amount <= 0) {
uni.showToast({ title: '订单金额无效', icon: 'error' })
return false
}
if (!orderData.shipping_address) {
uni.showToast({ title: '请选择收货地址', icon: 'error' })
return false
}
return true
}
八、性能优化
8.1 数据库优化
8.1.1 索引优化
-- 复合索引(针对常用查询组合)
CREATE INDEX idx_ml_products_status_created
ON ml_products(status, created_at DESC);
CREATE INDEX idx_ml_orders_user_status
ON ml_orders(user_id, order_status);
-- 部分索引(只索引活跃数据)
CREATE INDEX idx_ml_products_active
ON ml_products(merchant_id, category_id)
WHERE status = 1;
8.1.2 查询优化
// 只查询需要的字段
const res = await supa.select('ml_products', null, {
columns: 'id,name,base_price,main_image_url', // 只查询必要字段
limit: 20
})
// 使用视图简化复杂查询
const res = await supa.select('ml_products_detail_view', {
status: 1
}, {
limit: 20
})
8.2 前端优化
8.2.1 分页加载
// 分页加载商品列表
const loadProducts = async (loadMore: boolean = false) => {
const currentPage = loadMore ? page.value + 1 : 1
const res = await supa
.from('ml_products')
.select('*')
.eq('status', 1)
.order('created_at', { ascending: false })
.range((currentPage - 1) * pageSize.value, currentPage * pageSize.value - 1)
// 追加或替换数据
if (loadMore) {
productList.value.push(...res.data)
} else {
productList.value = res.data
}
}
8.2.2 数据缓存
// 使用本地缓存减少请求
const getCachedProducts = async (categoryId: string) => {
const cacheKey = `products_${categoryId}`
const cached = uni.getStorageSync(cacheKey)
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
// 5分钟内使用缓存
return cached.data
}
// 从服务器获取
const res = await supa.select('ml_products', {
category_id: categoryId,
status: 1
})
// 更新缓存
uni.setStorageSync(cacheKey, {
data: res.data,
timestamp: Date.now()
})
return res.data
}
8.3 实时同步优化
// 只订阅必要的数据变更
const subscribeOrderUpdates = (orderId: string) => {
realtime.subscribe('ml_orders', {
filter: `id=eq.${orderId}`, // 只订阅特定订单
event: 'UPDATE', // 只监听更新事件
callback: handleOrderUpdate
})
}
// 及时取消订阅
onUnmounted(() => {
realtime.unsubscribe('ml_orders')
})
九、开发工作流
9.1 开发流程
1. 数据库设计
│
├─ 设计表结构
├─ 设计索引
├─ 设计 RLS 策略
├─ 设计触发器
└─ 设计函数
│
▼
2. 执行数据库脚本
│
├─ complete_mall_database.sql
│
▼
3. 定义类型
│
├─ types/mall-types.uts
│
▼
4. 开发前端页面
│
├─ 页面组件
├─ API 调用
└─ 业务逻辑
│
▼
5. 测试验证
│
├─ 功能测试
├─ 权限测试
└─ 性能测试
9.2 调试技巧
// 1. 启用详细日志
console.log('请求参数:', {
table: 'ml_products',
filter: filter,
options: options
})
// 2. 检查响应
console.log('API 响应:', {
success: res.success,
status: res.status,
data: res.data,
error: res.error
})
// 3. 验证权限
const session = await supa.getSession()
console.log('当前用户:', session.user)
console.log('JWT Token:', AkReq.getToken())
📚 相关文档
生成时间: 2025年1月
版本: v1.0
状态: ✅ 完整技术实现拆解