Files
medical-mall/doc_mall/TECHNICAL_IMPLEMENTATION.md
2026-01-21 12:12:22 +08:00

41 KiB
Raw Blame History

🔨 doc_mall 模块技术实现拆解

📋 目录

  1. 整体架构
  2. 数据库层实现
  3. 后端/API层实现
  4. 前端实现
  5. 数据流机制
  6. 业务逻辑实现
  7. 安全机制
  8. 性能优化

一、整体架构

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 → 前端调用
  • 实现:
    1. 设计数据库表结构
    2. PostgREST 自动生成 REST API
    3. 前端通过 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
状态: 完整技术实现拆解