1432 lines
41 KiB
Markdown
1432 lines
41 KiB
Markdown
# 🔨 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
|
||
**状态**: ✅ 完整技术实现拆解
|