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

1432 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔨 doc_mall 模块技术实现拆解
## 📋 目录
1. [整体架构](#整体架构)
2. [数据库层实现](#数据库层实现)
3. [后端/API层实现](#后端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`)
```sql
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`)
```sql
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`)
```sql
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 性能优化索引
```sql
-- 商品表索引
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 自动更新时间戳
```sql
-- 触发器函数
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 确保唯一默认地址
```sql
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 自动更新商品库存
```sql
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 业务函数
**生成订单号**
```sql
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;
```
**计算购物车总金额**
```sql
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;
```
**获取用户默认地址**
```sql
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 用户数据隔离
```sql
-- 用户只能访问自己的数据
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 商品权限控制
```sql
-- 所有人可查看已上架商品
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 订单权限控制
```sql
-- 用户和商家都可查看相关订单
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 支持丰富的查询操作符:
```typescript
// 等于
?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 分页和排序
```typescript
// 分页
?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`
```typescript
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`
```typescript
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`
```typescript
<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 商品详情页面
```typescript
// 加载商品详情
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 购物车操作
```typescript
// 添加到购物车
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 创建订单
```typescript
// 创建订单
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 订阅订单状态更新
```typescript
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 商品上架流程
```typescript
// 商家上架商品
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 订单状态流转
```typescript
// 订单状态枚举
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 库存扣减流程
```typescript
// 下单时扣减库存
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 数据库约束
```sql
-- 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 前端验证
```typescript
// 表单验证
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 索引优化
```sql
-- 复合索引(针对常用查询组合)
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 查询优化
```typescript
// 只查询需要的字段
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 分页加载
```typescript
// 分页加载商品列表
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 数据缓存
```typescript
// 使用本地缓存减少请求
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 实时同步优化
```typescript
// 只订阅必要的数据变更
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 调试技巧
```typescript
// 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())
```
---
## 📚 相关文档
- [模块分析报告](./MODULE_ANALYSIS.md)
- [前后端联调指南](./FRONTEND_BACKEND_DEBUGGING.md)
- [数据库创建报告](./database/database_creation_report.md)
- [完整部署指南](./database/complete_deployment_guide.md)
---
**生成时间**: 2025年1月
**版本**: v1.0
**状态**: ✅ 完整技术实现拆解