数据库文档编写,开发规范文档,数据库接入
This commit is contained in:
32
docs/sql/00_overview.md
Normal file
32
docs/sql/00_overview.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 00 概览:商城数据库总体设计
|
||||
|
||||
## 目标与定位
|
||||
|
||||
本数据库设计面向 **PostgreSQL + Supabase** 的电商/订阅混合业务,核心目标:
|
||||
|
||||
- **统一用户体系复用**:复用 `public.ak_users`,商城域只做扩展(`ml_` 前缀)。
|
||||
- **安全优先(Supabase 直连友好)**:使用 RLS(Row Level Security)+ `auth.uid()` 做行级数据隔离。
|
||||
- **对外访问友好(SEO / URL)**:核心表同时提供 `UUID id`(内部主键)与 `SERIAL cid`(对外可读 ID),配合 `slug` 与 SEO 函数。
|
||||
- **数据库承载关键一致性**:触发器/函数/约束实现 `updated_at` 自动维护、默认地址唯一、库存汇总、订单状态时间戳与销量累计等。
|
||||
- **快速迭代与可扩展**:大量使用 `JSONB` 承载可变结构(属性、媒体、地址快照、适用范围、订阅 features/metadata 等)。
|
||||
|
||||
## SQL 资料来源
|
||||
|
||||
- 迁移/建库主脚本:
|
||||
- `doc_mall/database/mall_migration.sql`(偏幂等、增量迁移)
|
||||
- `doc_mall/database/complete_mall_database.sql`(偏一次性完整初始化,包含更多视图/函数/RLS/SEO)
|
||||
- 订阅模块:`doc_mall/create_mall_subscription_tables.sql`
|
||||
- 检查/测试脚本:`mall_sql/tests/mall_database_check.sql` 等
|
||||
|
||||
## 术语
|
||||
|
||||
- **SPU**:商品主表(本库对应 `ml_products`)
|
||||
- **SKU**:商品规格明细(本库对应 `ml_product_skus`)
|
||||
- **RLS**:Row Level Security(行级安全策略)
|
||||
|
||||
## 一句话总结
|
||||
|
||||
- **Supabase 优先**(RLS + `auth.uid()`)
|
||||
- **快速迭代优先**(JSONB + 配置化)
|
||||
- **数据库保证关键一致性**(触发器/函数/约束/视图)
|
||||
- **对外可读与 SEO 友好**(`UUID id` + `SERIAL cid` + `slug`)
|
||||
236
docs/sql/01_tables_catalog.md
Normal file
236
docs/sql/01_tables_catalog.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# 01 表清单与职责划分(按业务域)
|
||||
|
||||
本节将 `ml_` 前缀的商城域表按业务域归类,并给出每张表的核心职责、关键字段与主要关联。
|
||||
|
||||
> 说明:用户主表复用 `public.ak_users`,商城域通过外键 `user_id/merchant_id` 关联。
|
||||
|
||||
---
|
||||
|
||||
## 1. 用户域(Account Extension)
|
||||
|
||||
### 1.1 `ml_user_profiles`(用户扩展档案)
|
||||
|
||||
- **职责**:承载商城侧的用户扩展信息(状态、实名、信用分、认证信息、偏好等)。
|
||||
- **关键字段**:
|
||||
- `user_id`:外键到 `ak_users(id)`(且 `UNIQUE`,保证一用户一档案)
|
||||
- `status`:用户状态(正常/冻结/注销/待审核)
|
||||
- `verification_status`、`verification_data`:认证状态及数据(JSONB)
|
||||
- `preferences`:用户偏好(JSONB)
|
||||
- **主要关联**:
|
||||
- `ml_user_profiles.user_id -> ak_users.id`
|
||||
|
||||
### 1.2 `ml_user_addresses`(用户地址)
|
||||
|
||||
- **职责**:用户收货地址(并支持默认地址)。
|
||||
- **关键字段**:
|
||||
- `user_id`:外键到 `ak_users(id)`
|
||||
- `is_default`:是否默认地址(由触发器确保单一默认)
|
||||
- `province/city/district/address_detail`:地址结构化字段
|
||||
- **主要关联**:
|
||||
- `ml_user_addresses.user_id -> ak_users.id`
|
||||
|
||||
---
|
||||
|
||||
## 2. 商品域(Catalog)
|
||||
|
||||
### 2.1 `ml_categories`(商品分类)
|
||||
|
||||
- **职责**:商品分类树,支持 SEO(`cid/slug`)与层级路径。
|
||||
- **关键字段**:
|
||||
- `parent_id`:自关联
|
||||
- `path TEXT[]`:分类路径(便于面包屑、筛选等)
|
||||
- `cid`:对外友好 ID
|
||||
- `slug`:SEO slug
|
||||
- **主要关联**:
|
||||
- `ml_categories.parent_id -> ml_categories.id`
|
||||
|
||||
### 2.2 `ml_brands`(品牌)
|
||||
|
||||
- **职责**:品牌维度,支持 SEO(`cid`)。
|
||||
- **关键字段**:`name/logo_url/is_active/cid`
|
||||
|
||||
### 2.3 `ml_products`(商品 SPU)
|
||||
|
||||
- **职责**:商品主表(SPU),包含:定价、库存汇总、SEO、属性、统计。
|
||||
- **关键字段**:
|
||||
- `merchant_id`:商家(关联 `ak_users`)
|
||||
- `category_id`、`brand_id`
|
||||
- `base_price/market_price/cost_price`
|
||||
- `total_stock/available_stock`(由 SKU 触发器汇总维护)
|
||||
- `status`(上架/下架/草稿/删除)
|
||||
- `image_urls/video_urls/attributes`(JSONB)
|
||||
- `cid/slug/seo_*`
|
||||
- **主要关联**:
|
||||
- `ml_products.merchant_id -> ak_users.id`
|
||||
- `ml_products.category_id -> ml_categories.id`
|
||||
- `ml_products.brand_id -> ml_brands.id`
|
||||
|
||||
### 2.4 `ml_product_skus`(商品 SKU)
|
||||
|
||||
- **职责**:SKU 明细:规格组合、SKU 价格与库存。
|
||||
- **关键字段**:
|
||||
- `product_id`:所属 SPU
|
||||
- `specifications JSONB`:规格组合(例如 `{color:"black", size:"M"}`)
|
||||
- `price/stock/status`
|
||||
- **主要关联**:
|
||||
- `ml_product_skus.product_id -> ml_products.id`
|
||||
|
||||
### 2.5 `ml_product_specs`(商品规格定义)
|
||||
|
||||
- **职责**:描述一个商品有哪些规格项及可选值(用于生成 SKU)。
|
||||
- **关键字段**:
|
||||
- `spec_name`(如 颜色/尺寸)
|
||||
- `spec_values JSONB`(如 `["black","white"]`)
|
||||
|
||||
---
|
||||
|
||||
## 3. 店铺域(Merchant/Shop)
|
||||
|
||||
### `ml_shops`(店铺)
|
||||
|
||||
- **职责**:店铺信息(当前模型约束“一商家一店”)。
|
||||
- **关键字段**:
|
||||
- `merchant_id UNIQUE`:一对一约束
|
||||
- `status`(正常/暂停/关闭)
|
||||
- `address/business_hours`(JSONB)
|
||||
- `rating_avg/rating_count/product_count/order_count`(统计类)
|
||||
|
||||
---
|
||||
|
||||
## 4. 交易域(Order/Trade)
|
||||
|
||||
### 4.1 `ml_orders`(订单主表)
|
||||
|
||||
- **职责**:订单交易核心,含金额、地址快照、状态机、关键时间点。
|
||||
- **关键字段**:
|
||||
- `order_no`:订单号(可由序列+函数生成)
|
||||
- `user_id`:买家
|
||||
- `merchant_id`:商家(当前单商家订单模型)
|
||||
- 金额拆分:`product_amount/discount_amount/shipping_fee/total_amount/paid_amount`
|
||||
- `shipping_address JSONB`:下单快照地址
|
||||
- 状态机:`order_status/payment_status/shipping_status`
|
||||
- 时间点:`paid_at/shipped_at/delivered_at/completed_at`
|
||||
|
||||
### 4.2 `ml_order_items`(订单明细)
|
||||
|
||||
- **职责**:订单行项目,保存下单快照(防止商品信息后改影响历史订单)。
|
||||
- **关键字段**:
|
||||
- `order_id` 外键
|
||||
- `product_id/sku_id`
|
||||
- `product_name/sku_name/specifications/image_url/price`(快照冗余)
|
||||
- `quantity/total_amount`
|
||||
|
||||
---
|
||||
|
||||
## 5. 购物车域
|
||||
|
||||
### `ml_shopping_cart`
|
||||
|
||||
- **职责**:购物车行项目。
|
||||
- **关键字段**:
|
||||
- `user_id/product_id/sku_id`
|
||||
- `quantity`、`selected`
|
||||
- `UNIQUE(user_id, product_id, sku_id)`:避免重复行
|
||||
|
||||
---
|
||||
|
||||
## 6. 营销域(Coupon)
|
||||
|
||||
### 6.1 `ml_coupon_templates`(优惠券模板)
|
||||
|
||||
- **职责**:券模板定义;支持平台券(`merchant_id` 为空)与商家券。
|
||||
- **关键字段**:
|
||||
- `coupon_type`(满减/折扣/免邮)
|
||||
- `discount_type`(固定金额/百分比)
|
||||
- `discount_value/min_order_amount/max_discount_amount`
|
||||
- `applicable_products/applicable_categories`(JSONB)
|
||||
- `start_time/end_time/status`
|
||||
|
||||
### 6.2 `ml_user_coupons`(用户优惠券)
|
||||
|
||||
- **职责**:用户领取的券实例,包含券码与使用归因。
|
||||
- **关键字段**:
|
||||
- `coupon_code`(唯一)
|
||||
- `status`(未用/已用/过期)
|
||||
- `used_at/order_id`
|
||||
|
||||
---
|
||||
|
||||
## 7. 履约域(Delivery)
|
||||
|
||||
### 7.1 `ml_delivery_drivers`(配送员)
|
||||
|
||||
- **职责**:配送员信息与工作状态。
|
||||
- **关键字段**:
|
||||
- `user_id UNIQUE`:一个用户对应一个配送员档案
|
||||
- `work_status/status`、位置信息、统计与评分
|
||||
|
||||
### 7.2 `ml_delivery_tasks`(配送任务)
|
||||
|
||||
- **职责**:配送任务与订单 1:1 绑定。
|
||||
- **关键字段**:
|
||||
- `order_id UNIQUE`:一个订单最多一个配送任务
|
||||
- `driver_id`
|
||||
- `pickup_address/delivery_address`(JSONB 快照)
|
||||
- `status`、`assigned_at/picked_at/delivered_at`
|
||||
|
||||
---
|
||||
|
||||
## 8. 评价域(Review)
|
||||
|
||||
### `ml_product_reviews`
|
||||
|
||||
- **职责**:商品评价;通过 `order_id + order_item_id` 强绑定订单来源。
|
||||
- **关键字段**:
|
||||
- `order_id`、`order_item_id`
|
||||
- `rating/content/images`
|
||||
- `merchant_reply/merchant_replied_at`
|
||||
|
||||
---
|
||||
|
||||
## 9. 用户行为域(Behavior)
|
||||
|
||||
### 9.1 `ml_user_favorites`(收藏)
|
||||
|
||||
- **职责**:收藏(多态目标:商品/店铺)。
|
||||
- **关键字段**:
|
||||
- `target_type`(1 商品 / 2 店铺)
|
||||
- `target_id`
|
||||
- `UNIQUE(user_id, target_type, target_id)`
|
||||
|
||||
### 9.2 `ml_browse_history`(浏览历史)
|
||||
|
||||
- **职责**:浏览记录(当前模型倾向“同商品最后一次浏览覆盖”)。
|
||||
- **关键字段**:`UNIQUE(user_id, product_id)`
|
||||
|
||||
### 9.3 `ml_search_history`(搜索记录)
|
||||
|
||||
- **职责**:搜索日志(可用于热词/推荐/分析)。
|
||||
- **关键字段**:`keyword/result_count/ip_address/user_agent`
|
||||
|
||||
---
|
||||
|
||||
## 10. 配置与地区
|
||||
|
||||
### 10.1 `ml_system_configs`(系统配置)
|
||||
|
||||
- **职责**:配置中心(JSONB),如运费、佣金比例、订单自动确认天数等。
|
||||
- **关键字段**:`config_key` 唯一、`config_value JSONB`
|
||||
|
||||
### 10.2 `ml_regions`(地区)
|
||||
|
||||
- **职责**:地区树(省/市/区/街道)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 订阅模块(Subscription)
|
||||
|
||||
### 11.1 `ml_subscription_plans`(订阅套餐)
|
||||
|
||||
- **职责**:订阅计划定义(计费周期、价格、试用天数、features)。
|
||||
- **关键字段**:`plan_code` 唯一、`billing_period`、`features JSONB`
|
||||
|
||||
### 11.2 `ml_user_subscriptions`(用户订阅)
|
||||
|
||||
- **职责**:用户订阅实例(状态机、自动续费、下次扣费时间等)。
|
||||
- **关键字段**:`status`(trial/active/past_due/canceled/expired)、`auto_renew`、`metadata JSONB`
|
||||
147
docs/sql/02_relationships_er.md
Normal file
147
docs/sql/02_relationships_er.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 02 关系与 ER(文字版)
|
||||
|
||||
本节用“文字版 ER + 基数(1:1 / 1:N)”描述核心表关系,并提示哪些约束来自数据库(唯一约束/外键/触发器)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 统一用户体系(复用 `ak_users`)
|
||||
|
||||
- `ak_users` 作为统一用户主表,商城域表通过 `user_id/merchant_id` 外键关联。
|
||||
|
||||
> 重要前提:RLS 策略通过 `auth.uid()` 映射 `ak_users.auth_id`(详见 `05_rls_permissions_matrix.md`)。因此 `ak_users` 必须具备 `auth_id` 字段并保持唯一性(建议 `unique index`)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 用户域
|
||||
|
||||
### 2.1 `ak_users` 1:1 `ml_user_profiles`
|
||||
|
||||
- **关系**:一用户一商城档案
|
||||
- **依据**:`ml_user_profiles.user_id UNIQUE NOT NULL REFERENCES ak_users(id)`
|
||||
|
||||
### 2.2 `ak_users` 1:N `ml_user_addresses`
|
||||
|
||||
- **关系**:一个用户多个地址
|
||||
- **默认地址约束**:同一用户最多一个 `is_default = true`
|
||||
- **依据**:触发器 `ensure_single_default_address()`(数据库层自动维护)
|
||||
|
||||
---
|
||||
|
||||
## 3. 店铺/商家域
|
||||
|
||||
### 3.1 `ak_users(merchant)` 1:1 `ml_shops`
|
||||
|
||||
- **关系**:一个商家一个店铺(当前模型)
|
||||
- **依据**:`ml_shops.merchant_id UNIQUE NOT NULL REFERENCES ak_users(id)`
|
||||
|
||||
> 影响:订单主表可直接记录 `merchant_id`,无需子订单拆分即可查询店铺信息。
|
||||
|
||||
---
|
||||
|
||||
## 4. 商品域
|
||||
|
||||
### 4.1 `ml_categories` 1:N `ml_categories`(自关联分类树)
|
||||
|
||||
- **关系**:父分类包含多个子分类
|
||||
- **依据**:`ml_categories.parent_id REFERENCES ml_categories(id)`
|
||||
|
||||
### 4.2 `ml_categories` 1:N `ml_products`
|
||||
|
||||
- **关系**:一个分类包含多个商品
|
||||
- **依据**:`ml_products.category_id NOT NULL REFERENCES ml_categories(id)`
|
||||
|
||||
### 4.3 `ml_brands` 1:N `ml_products`
|
||||
|
||||
- **关系**:一个品牌对应多个商品
|
||||
- **依据**:`ml_products.brand_id REFERENCES ml_brands(id)`(可空)
|
||||
|
||||
### 4.4 `ml_products` 1:N `ml_product_skus`
|
||||
|
||||
- **关系**:一个商品(SPU)有多个 SKU
|
||||
- **依据**:`ml_product_skus.product_id NOT NULL REFERENCES ml_products(id) ON DELETE CASCADE`
|
||||
|
||||
### 4.5 `ml_products` 1:N `ml_product_specs`
|
||||
|
||||
- **关系**:一个商品定义多个规格项
|
||||
- **依据**:`ml_product_specs.product_id NOT NULL REFERENCES ml_products(id) ON DELETE CASCADE`
|
||||
|
||||
### 4.6 库存汇总(触发器关系)
|
||||
|
||||
- **事件**:`ml_product_skus` INSERT/UPDATE/DELETE
|
||||
- **结果**:触发器 `update_product_stock()` 汇总更新 `ml_products.total_stock/available_stock`
|
||||
|
||||
---
|
||||
|
||||
## 5. 交易域
|
||||
|
||||
### 5.1 `ak_users(customer)` 1:N `ml_orders`
|
||||
|
||||
- **关系**:用户有多个订单
|
||||
- **依据**:`ml_orders.user_id NOT NULL REFERENCES ak_users(id)`
|
||||
|
||||
### 5.2 `ak_users(merchant)` 1:N `ml_orders`
|
||||
|
||||
- **关系**:商家有多个订单
|
||||
- **依据**:`ml_orders.merchant_id NOT NULL REFERENCES ak_users(id)`
|
||||
|
||||
> 当前订单模型为“单商家订单”(`ml_orders` 直接记录 `merchant_id`)。若要支持“一单多商家”,通常需要主/子订单拆分。
|
||||
|
||||
### 5.3 `ml_orders` 1:N `ml_order_items`
|
||||
|
||||
- **关系**:订单包含多个明细
|
||||
- **依据**:`ml_order_items.order_id NOT NULL REFERENCES ml_orders(id) ON DELETE CASCADE`
|
||||
|
||||
### 5.4 `ml_orders` 1:1 `ml_delivery_tasks`(当前)
|
||||
|
||||
- **关系**:一个订单最多一个配送任务
|
||||
- **依据**:`ml_delivery_tasks.order_id UNIQUE NOT NULL REFERENCES ml_orders(id)`
|
||||
|
||||
---
|
||||
|
||||
## 6. 营销域
|
||||
|
||||
### 6.1 `ml_coupon_templates` 1:N `ml_user_coupons`
|
||||
|
||||
- **关系**:一个模板可被多个用户领取
|
||||
- **依据**:`ml_user_coupons.template_id NOT NULL REFERENCES ml_coupon_templates(id)`
|
||||
|
||||
### 6.2 `ml_user_coupons` N:1 `ml_orders`(可选关联)
|
||||
|
||||
- **关系**:券在使用时关联订单
|
||||
- **依据**:`ml_user_coupons.order_id REFERENCES ml_orders(id)`(可空)
|
||||
|
||||
---
|
||||
|
||||
## 7. 评价域
|
||||
|
||||
### 7.1 `ml_orders` 1:N `ml_product_reviews`(概念上)
|
||||
|
||||
- **关系**:订单可产生多个评价(通常按明细评价)
|
||||
- **依据**:`ml_product_reviews.order_id NOT NULL REFERENCES ml_orders(id)`
|
||||
|
||||
### 7.2 `ml_order_items` 1:1/N `ml_product_reviews`
|
||||
|
||||
- **关系**:明细可对应评价(实现上可以 1:1,也可以允许追评,取决于业务约束)
|
||||
- **依据**:`ml_product_reviews.order_item_id NOT NULL REFERENCES ml_order_items(id)`
|
||||
|
||||
---
|
||||
|
||||
## 8. 行为域
|
||||
|
||||
- `ak_users` 1:N `ml_user_favorites`
|
||||
- `ak_users` 1:N `ml_browse_history`
|
||||
- `ak_users` 0..N `ml_search_history`(可匿名搜索时 user_id 为空)
|
||||
|
||||
---
|
||||
|
||||
## 9. 订阅域
|
||||
|
||||
### 9.1 `ml_subscription_plans` 1:N `ml_user_subscriptions`
|
||||
|
||||
- **关系**:一个套餐对应多个用户订阅实例
|
||||
- **依据**:`ml_user_subscriptions.plan_id REFERENCES ml_subscription_plans(id)`
|
||||
|
||||
### 9.2 `ak_users` 1:N `ml_user_subscriptions`
|
||||
|
||||
- **关系**:一个用户可有多个订阅记录(例如历史续费、升级降级)
|
||||
- **依据**:`ml_user_subscriptions.user_id`(当前脚本未声明外键到 `ak_users`,建议在项目侧补齐或在应用层保证一致性)
|
||||
213
docs/sql/03_enums_status_dict.md
Normal file
213
docs/sql/03_enums_status_dict.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# 03 状态/枚举字典(统一口径)
|
||||
|
||||
本节汇总数据库中以 `INTEGER + CHECK` 或 `TEXT + CHECK` 形式出现的核心状态字段,给出建议的统一解释口径。
|
||||
|
||||
> 注意:部分状态值在 `mall_migration.sql` 与 `complete_mall_database.sql` 存在细微差异(例如订单取消/取货的命名)。本字典以“脚本中出现的实际取值范围”为准,并在差异处标注。
|
||||
|
||||
---
|
||||
|
||||
## 1. 用户与认证
|
||||
|
||||
### 1.1 `ml_user_profiles.status`
|
||||
|
||||
取值:`IN (1,2,3,4)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:冻结
|
||||
- `3`:注销
|
||||
- `4`:待审核
|
||||
|
||||
### 1.2 `ml_user_profiles.verification_status`
|
||||
|
||||
取值:`IN (0,1,2)`
|
||||
|
||||
- `0`:未认证
|
||||
- `1`:已认证
|
||||
- `2`:认证失败
|
||||
|
||||
### 1.3 `ml_user_addresses.status`
|
||||
|
||||
取值:`IN (1,2)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:禁用
|
||||
|
||||
---
|
||||
|
||||
## 2. 商品
|
||||
|
||||
### 2.1 `ml_products.status`
|
||||
|
||||
取值:`IN (1,2,3,4)`
|
||||
|
||||
- `1`:上架
|
||||
- `2`:下架
|
||||
- `3`:草稿
|
||||
- `4`:删除(逻辑删除)
|
||||
|
||||
### 2.2 `ml_product_skus.status`
|
||||
|
||||
取值:`IN (1,2)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:禁用
|
||||
|
||||
---
|
||||
|
||||
## 3. 店铺
|
||||
|
||||
### `ml_shops.status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:暂停
|
||||
- `3`:关闭
|
||||
|
||||
---
|
||||
|
||||
## 4. 订单(交易状态机)
|
||||
|
||||
> 订单存在三个并行状态字段:`order_status`(订单流程)、`payment_status`(支付/退款)、`shipping_status`(发货/物流)。
|
||||
|
||||
### 4.1 `ml_orders.order_status`
|
||||
|
||||
取值:`IN (1,2,3,4,5,6,7)`
|
||||
|
||||
- `1`:待付款
|
||||
- `2`:待发货(在 `complete` 脚本里也可能被解释为“已付款/待发货”)
|
||||
- `3`:待收货
|
||||
- `4`:已完成
|
||||
- `5`:已取消 / 已取货(不同脚本表述不一致,建议在业务层统一为“取消”或“自提完成”之一)
|
||||
- `6`:退款中
|
||||
- `7`:已退款
|
||||
|
||||
建议(文档口径):
|
||||
|
||||
- 若业务没有“自提/取货”流程,建议将 `5` 固化为“已取消”。
|
||||
- 若业务需要“自提/取货完成”,建议拆出更清晰的状态(例如新增 `8` 表示取货完成),并迁移更新 CHECK。
|
||||
|
||||
### 4.2 `ml_orders.payment_status`
|
||||
|
||||
取值:`IN (1,2,3,4)`
|
||||
|
||||
- `1`:未付款
|
||||
- `2`:已付款
|
||||
- `3`:部分退款
|
||||
- `4`:全额退款
|
||||
|
||||
### 4.3 `ml_orders.shipping_status`
|
||||
|
||||
取值:`IN (1,2,3,4)`
|
||||
|
||||
- `1`:未发货
|
||||
- `2`:已发货
|
||||
- `3`:运输中
|
||||
- `4`:已送达
|
||||
|
||||
---
|
||||
|
||||
## 5. 优惠券
|
||||
|
||||
### 5.1 `ml_coupon_templates.coupon_type`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:满减券
|
||||
- `2`:折扣券
|
||||
- `3`:免运费券
|
||||
|
||||
### 5.2 `ml_coupon_templates.discount_type`
|
||||
|
||||
取值:`IN (1,2)`
|
||||
|
||||
- `1`:固定金额
|
||||
- `2`:百分比
|
||||
|
||||
### 5.3 `ml_coupon_templates.status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:暂停
|
||||
- `3`:已结束
|
||||
|
||||
### 5.4 `ml_user_coupons.status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:未使用
|
||||
- `2`:已使用
|
||||
- `3`:已过期
|
||||
|
||||
---
|
||||
|
||||
## 6. 配送
|
||||
|
||||
### 6.1 `ml_delivery_drivers.vehicle_type`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:电动车
|
||||
- `2`:摩托车
|
||||
- `3`:汽车
|
||||
|
||||
### 6.2 `ml_delivery_drivers.work_status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:在线
|
||||
- `2`:忙碌
|
||||
- `3`:离线
|
||||
|
||||
### 6.3 `ml_delivery_drivers.status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:暂停
|
||||
- `3`:离职
|
||||
|
||||
### 6.4 `ml_delivery_tasks.status`
|
||||
|
||||
取值:`IN (1,2,3,4,5,6)`
|
||||
|
||||
- `1`:待接单
|
||||
- `2`:已接单
|
||||
- `3`:取货中
|
||||
- `4`:配送中
|
||||
- `5`:已送达
|
||||
- `6`:配送失败
|
||||
|
||||
---
|
||||
|
||||
## 7. 评价与行为
|
||||
|
||||
### 7.1 `ml_product_reviews.status`
|
||||
|
||||
取值:`IN (1,2,3)`
|
||||
|
||||
- `1`:正常
|
||||
- `2`:已删除
|
||||
- `3`:已隐藏
|
||||
|
||||
### 7.2 `ml_user_favorites.target_type`
|
||||
|
||||
取值:`IN (1,2)`
|
||||
|
||||
- `1`:商品
|
||||
- `2`:店铺
|
||||
|
||||
---
|
||||
|
||||
## 8. 订阅(Subscription)
|
||||
|
||||
### `ml_user_subscriptions.status`
|
||||
|
||||
取值:`IN ('trial','active','past_due','canceled','expired')`
|
||||
|
||||
- `trial`:试用中
|
||||
- `active`:生效中
|
||||
- `past_due`:逾期(扣费失败/欠费)
|
||||
- `canceled`:已取消
|
||||
- `expired`:已过期
|
||||
240
docs/sql/04_triggers_and_functions.md
Normal file
240
docs/sql/04_triggers_and_functions.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 04 触发器与函数(数据库承载的业务规则)
|
||||
|
||||
本节汇总数据库内实现的关键触发器与函数,说明:
|
||||
|
||||
- 它们解决什么业务问题
|
||||
- 触发时机是什么
|
||||
- 对数据一致性与性能的影响
|
||||
- 典型使用/触发示例
|
||||
|
||||
---
|
||||
|
||||
## 1. 通用触发器:自动维护 `updated_at`
|
||||
|
||||
### 1.1 `public.update_updated_at_column()`(complete 脚本)
|
||||
|
||||
- **目的**:统一把 `updated_at` 设置为当前时间,避免应用层漏写。
|
||||
- **触发时机**:`BEFORE UPDATE`
|
||||
|
||||
典型触发表(complete 脚本中出现):
|
||||
|
||||
- `ml_user_profiles`
|
||||
- `ml_user_addresses`
|
||||
- `ml_products`
|
||||
- `ml_product_skus`
|
||||
- `ml_shops`
|
||||
- `ml_orders`
|
||||
- `ml_shopping_cart`
|
||||
|
||||
触发效果示例:
|
||||
|
||||
```sql
|
||||
update public.ml_products
|
||||
set name = '新标题'
|
||||
where id = '...product_uuid...'::uuid;
|
||||
|
||||
-- updated_at 会自动变为 now()
|
||||
```
|
||||
|
||||
### 1.2 `public.set_updated_at()`(订阅脚本)
|
||||
|
||||
订阅模块在 `doc_mall/create_mall_subscription_tables.sql` 里定义了一个更轻量的 `set_updated_at()`,并对:
|
||||
|
||||
- `ml_subscription_plans`
|
||||
- `ml_user_subscriptions`
|
||||
|
||||
设置 `BEFORE UPDATE` 触发器。
|
||||
|
||||
---
|
||||
|
||||
## 2. 地址一致性:默认地址唯一
|
||||
|
||||
### `public.ensure_single_default_address()`
|
||||
|
||||
- **目的**:保证同一个用户最多只有一个 `is_default = true` 的地址。
|
||||
- **触发时机**:`BEFORE INSERT OR UPDATE ON ml_user_addresses`
|
||||
- **核心逻辑**:当新行/更新行被设为默认时,把该用户其他地址全部置为非默认。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_user_addresses
|
||||
set is_default = true
|
||||
where id = '...address_uuid...'::uuid;
|
||||
|
||||
-- 触发器会把同 user_id 的其他地址 is_default 设为 false
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 这是“业务规则下沉 DB”的典型。
|
||||
- 若存在并发更新两条地址为默认,最终仍会收敛为“最后提交事务的那条为默认”。
|
||||
|
||||
---
|
||||
|
||||
## 3. 库存汇总:SKU 维护,SPU 汇总
|
||||
|
||||
### `public.update_product_stock()`
|
||||
|
||||
- **目的**:当 SKU 改变时,自动汇总刷新商品表的库存字段,避免每次展示都 `join + group by`。
|
||||
- **触发时机**:`AFTER INSERT OR UPDATE OR DELETE ON ml_product_skus`
|
||||
- **影响字段**:
|
||||
- `ml_products.total_stock`
|
||||
- `ml_products.available_stock`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_product_skus
|
||||
set stock = 8
|
||||
where id = '...sku_uuid...'::uuid;
|
||||
|
||||
-- 触发器会汇总该 product_id 下所有 status=1 的 SKU stock
|
||||
-- 并更新到 ml_products.total_stock / available_stock
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 这种“汇总字段”设计利于读性能,但写入 SKU 会产生额外更新 SPU 的成本。
|
||||
- 若 SKU 更新频率很高,需要评估热点商品写放大。
|
||||
|
||||
---
|
||||
|
||||
## 4. 订单状态副作用(complete 脚本)
|
||||
|
||||
### `public.handle_order_status_change()`
|
||||
|
||||
- **目的**:订单状态变化时,自动写入关键时间点,并在完成时累计销量。
|
||||
- **触发时机**:`BEFORE UPDATE ON ml_orders`
|
||||
|
||||
核心行为(按脚本逻辑):
|
||||
|
||||
- 从 `order_status: 1 -> 2`:写入 `paid_at = now()`
|
||||
- 从 `order_status: 2 -> 3`:写入 `shipped_at = now()`
|
||||
- 从 `order_status: 3 -> 4`:写入 `delivered_at/completed_at = now()`,并累计 `ml_products.sale_count`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 2
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- paid_at 自动写入
|
||||
```
|
||||
|
||||
销量累计示例(订单完成):
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 4
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- 会对订单明细涉及的商品 sale_count 做累加
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 若业务上“支付状态”与“订单状态”不是严格 1->2 的映射,可能需要调整触发条件。
|
||||
- 累计销量属于“统计字段”,适合下沉 DB;但需要考虑退款/取消是否回滚销量(脚本未体现)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 生成类函数
|
||||
|
||||
### 5.1 `public.generate_order_no()` + `public.ml_order_seq`
|
||||
|
||||
- **目的**:生成业务订单号(形如 `MLYYYYMMDD000001`)。
|
||||
- **依赖**:序列 `ml_order_seq`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.generate_order_no();
|
||||
```
|
||||
|
||||
建议:
|
||||
|
||||
- 若订单号生成需要“并发唯一 + 分库分表友好”,可以进一步引入节点号/随机段。
|
||||
|
||||
### 5.2 `public.generate_coupon_code()`
|
||||
|
||||
- **目的**:生成券码(脚本中为 `CP` + 8 位随机字符)。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.generate_coupon_code();
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 随机生成+唯一约束在极端高并发下可能出现冲突重试需求(一般可接受)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 查询辅助函数
|
||||
|
||||
### 6.1 `public.get_user_default_address(p_user_id uuid)`
|
||||
|
||||
- **目的**:快速获取用户默认地址,并拼接 `full_address`。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select *
|
||||
from public.get_user_default_address('...user_uuid...'::uuid);
|
||||
```
|
||||
|
||||
### 6.2 `public.calculate_cart_total(p_user_id uuid)`
|
||||
|
||||
- **目的**:计算用户购物车选中商品的总金额(按 SKU 价 * 数量)。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.calculate_cart_total('...user_uuid...'::uuid);
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 该函数读取多表(cart + sku + product),并包含状态过滤。
|
||||
- 若购物车行数很多或频繁调用,需要关注执行计划与索引。
|
||||
|
||||
### 6.3 `public.get_product_available_stock(p_product_id uuid, p_sku_id uuid default null)`
|
||||
|
||||
- **目的**:查询商品或指定 SKU 的可用库存。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
-- 查 SKU
|
||||
select public.get_product_available_stock('...product_uuid...'::uuid, '...sku_uuid...'::uuid);
|
||||
|
||||
-- 查商品汇总
|
||||
select public.get_product_available_stock('...product_uuid...'::uuid, null);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. SEO 相关函数(complete 脚本)
|
||||
|
||||
- `get_product_by_cid(p_cid int)`
|
||||
- `get_category_by_cid(p_cid int)`
|
||||
- `get_brand_by_cid(p_cid int)`
|
||||
- `get_shop_by_cid(p_cid int)`
|
||||
- `generate_seo_url(p_type, p_cid, p_slug)`
|
||||
- `update_seo_slugs()`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select * from public.get_category_by_cid(1001);
|
||||
select public.generate_seo_url('shop', 88, 'my-shop');
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- `update_seo_slugs()` 使用正则把名称转为 slug:
|
||||
- 适合初始化/批处理
|
||||
- 需要注意多语言、重复 slug、空字符等边界
|
||||
159
docs/sql/05_rls_permissions_matrix.md
Normal file
159
docs/sql/05_rls_permissions_matrix.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 05 RLS 权限矩阵(Supabase 行级安全)
|
||||
|
||||
本节整理 `complete_mall_database.sql` 中的 RLS(Row Level Security)启用范围与策略意图,并给出“角色 × 表 × 操作”的矩阵化视角,便于前后端对齐。
|
||||
|
||||
> 说明:该库采用 Supabase 模式,常用 `auth.uid()` 获取当前登录用户的 auth id,并通过 `ak_users.auth_id` 映射到业务用户 `ak_users.id`。
|
||||
|
||||
---
|
||||
|
||||
## 1. RLS 设计目标
|
||||
|
||||
- **默认拒绝**:启用 RLS 后,如果没有策略,访问会被拒绝。
|
||||
- **数据隔离优先**:用户私有数据只能访问自己的行。
|
||||
- **商家/用户双视角**:订单可被“买家”和“卖家”访问。
|
||||
- **公共可见数据受限**:商品仅公开上架数据。
|
||||
|
||||
---
|
||||
|
||||
## 2. 启用 RLS 的表(来自 `complete_mall_database.sql`)
|
||||
|
||||
脚本显式启用 RLS:
|
||||
|
||||
- `ml_user_profiles`
|
||||
- `ml_user_addresses`
|
||||
- `ml_shopping_cart`
|
||||
- `ml_user_favorites`
|
||||
- `ml_browse_history`
|
||||
- `ml_user_coupons`
|
||||
- `ml_orders`
|
||||
- `ml_products`
|
||||
|
||||
> 备注:其他表(如 `ml_categories/ml_brands/ml_shops/ml_order_items` 等)在该脚本片段中未显式启用 RLS。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心策略模式(pattern)
|
||||
|
||||
### 3.1 “归属自己”的通用模式
|
||||
|
||||
对用户私有表(档案、地址、购物车、收藏、浏览、券)使用类似逻辑:
|
||||
|
||||
- `SELECT/UPDATE/DELETE`:要求当前 `auth.uid()` 对应到该行的 `user_id`
|
||||
- `INSERT`:要求插入行的 `user_id` 也属于当前 `auth.uid()`
|
||||
|
||||
概念表达(伪 SQL):
|
||||
|
||||
```sql
|
||||
-- 伪表达:当前登录者只能操作 user_id 属于自己的行
|
||||
auth.uid() = (select auth_id from ak_users where id = <row.user_id>)
|
||||
```
|
||||
|
||||
价值:
|
||||
|
||||
- 前端直连 DB 时,**就算请求参数伪造 user_id**,也无法读写别人的行。
|
||||
|
||||
### 3.2 “订单:买家/卖家都可访问”模式
|
||||
|
||||
订单 SELECT 策略允许 `auth.uid()` 属于 `user_id` 或 `merchant_id`:
|
||||
|
||||
```sql
|
||||
auth.uid() in (
|
||||
select auth_id from ak_users where id in (user_id, merchant_id)
|
||||
)
|
||||
```
|
||||
|
||||
价值:
|
||||
|
||||
- 买家能看自己的订单
|
||||
- 商家能看自己店铺相关订单(在当前“单商家订单模型”下成立)
|
||||
|
||||
### 3.3 “商品:公开上架,商家管理自己的”模式
|
||||
|
||||
- `SELECT`:仅 `status = 1` 的商品可见
|
||||
- `INSERT/UPDATE/DELETE`:要求 `merchant_id` 属于当前登录商家
|
||||
|
||||
---
|
||||
|
||||
## 4. 权限矩阵(建议口径)
|
||||
|
||||
> 说明:此矩阵从业务语义出发描述“期望权限”。实际是否满足,还取决于:
|
||||
> - 是否启用 RLS
|
||||
> - 是否存在相应策略
|
||||
> - `ak_users` 中角色定义与 `auth_id` 映射是否正确
|
||||
|
||||
### 4.1 角色定义
|
||||
|
||||
- **Customer(消费者)**:普通用户
|
||||
- **Merchant(商家)**:拥有商品与订单管理权限
|
||||
- **Admin(管理员)**:平台管理(通常需要 service role 或额外策略)
|
||||
|
||||
### 4.2 表级矩阵(读/写)
|
||||
|
||||
#### `ml_user_profiles`
|
||||
|
||||
- Customer
|
||||
- **SELECT**:仅本人
|
||||
- **INSERT/UPDATE/DELETE**:仅本人
|
||||
- Merchant
|
||||
- 同 Customer(如果商家也是用户)
|
||||
- Admin
|
||||
- 建议:通过 service role 或单独策略可读全量
|
||||
|
||||
#### `ml_user_addresses`
|
||||
|
||||
- Customer
|
||||
- **SELECT/INSERT/UPDATE/DELETE**:仅本人
|
||||
|
||||
#### `ml_shopping_cart`
|
||||
|
||||
- Customer
|
||||
- **SELECT/INSERT/UPDATE/DELETE**:仅本人
|
||||
|
||||
#### `ml_user_favorites` / `ml_browse_history` / `ml_user_coupons`
|
||||
|
||||
- Customer
|
||||
- **SELECT/INSERT/UPDATE/DELETE**:仅本人
|
||||
|
||||
#### `ml_orders`
|
||||
|
||||
- Customer
|
||||
- **SELECT/INSERT/UPDATE/DELETE**:仅自己的订单
|
||||
- Merchant
|
||||
- **SELECT/INSERT/UPDATE/DELETE**:仅 `merchant_id` 为自己的订单
|
||||
- Admin
|
||||
- 建议:service role 或独立策略全量访问
|
||||
|
||||
#### `ml_products`
|
||||
|
||||
- Public / Customer
|
||||
- **SELECT**:仅上架(`status=1`)
|
||||
- Merchant
|
||||
- **SELECT**:至少能看上架;更合理的做法是:商家能看自己所有状态商品(当前策略是否支持需核对)
|
||||
- **INSERT/UPDATE/DELETE**:仅自己的商品
|
||||
|
||||
---
|
||||
|
||||
## 5. 关键前提与性能建议
|
||||
|
||||
### 5.1 `ak_users.auth_id` 的唯一性与索引
|
||||
|
||||
由于策略频繁执行子查询:
|
||||
|
||||
```sql
|
||||
select auth_id from ak_users where id = ...
|
||||
```
|
||||
|
||||
建议:
|
||||
|
||||
- 确保 `ak_users.id` 为主键(已有)
|
||||
- 确保 `ak_users.auth_id` 存在且唯一(建议唯一索引)
|
||||
|
||||
### 5.2 RLS 子查询的成本
|
||||
|
||||
RLS 每次查询都要执行策略表达式。若策略中大量子查询,可能带来性能压力。
|
||||
|
||||
可选优化方向:
|
||||
|
||||
- 在业务表冗余 `auth_id`(空间换性能)
|
||||
- 使用 `security definer` 函数封装策略逻辑(需谨慎)
|
||||
- 确保常用过滤字段(`user_id/merchant_id/status`)有索引
|
||||
179
docs/sql/06_indexes_and_query_patterns.md
Normal file
179
docs/sql/06_indexes_and_query_patterns.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 06 索引策略与典型查询模式
|
||||
|
||||
本节从“页面/接口会怎么查”出发解释索引的设计意图,并给出可复用的查询模式。
|
||||
|
||||
---
|
||||
|
||||
## 1. 索引总体思路
|
||||
|
||||
从 `complete_mall_database.sql` / `mall_migration.sql` 中可以看到索引集中在:
|
||||
|
||||
- 列表页高频过滤字段:`status`、`created_at`、`merchant_id`、`user_id`、`category_id`
|
||||
- 对外访问字段:`cid`、`slug`
|
||||
- 排序/榜单字段:`sale_count`、`rating_avg`、`rating_count`、`base_price`
|
||||
- 多值字段:`tags`(GIN)
|
||||
|
||||
其核心理念是:
|
||||
|
||||
- **读路径优先**:电商最常见的是“列表页 + 详情页”,索引优先覆盖这些路径。
|
||||
- **SEO 友好**:对外 URL 常用 `cid/slug`,因此为其建索引。
|
||||
- **避免重计算**:用触发器维护汇总字段(库存/销量),让查询尽量落在单表或轻量 join。
|
||||
|
||||
---
|
||||
|
||||
## 2. 典型查询模式与对应索引
|
||||
|
||||
> 注:以下 SQL 示例以可读性为主,实际项目可能通过视图(如 `ml_products_detail_view`)或 API 层封装。
|
||||
|
||||
### 2.1 商品列表页(按分类 + 上架状态 + 时间倒序)
|
||||
|
||||
典型查询:
|
||||
|
||||
```sql
|
||||
select id, cid, name, base_price, main_image_url, sale_count, rating_avg
|
||||
from public.ml_products
|
||||
where category_id = '...category_uuid...'::uuid
|
||||
and status = 1
|
||||
order by created_at desc
|
||||
limit 20 offset 0;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_products_category(category_id, status)`
|
||||
- `idx_ml_products_status(status, created_at desc)`
|
||||
|
||||
### 2.2 商品列表页(商家后台:按商家 + 状态)
|
||||
|
||||
```sql
|
||||
select id, cid, name, status, total_stock, sale_count
|
||||
from public.ml_products
|
||||
where merchant_id = '...merchant_uuid...'::uuid
|
||||
order by updated_at desc
|
||||
limit 50;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_products_merchant(merchant_id, status)`(也会被 merchant_id 过滤利用)
|
||||
|
||||
### 2.3 商品详情页(按 cid 或 slug)
|
||||
|
||||
```sql
|
||||
-- 方式 1:cid
|
||||
select * from public.get_product_by_cid(12345);
|
||||
|
||||
-- 方式 2:slug
|
||||
select *
|
||||
from public.ml_products
|
||||
where slug = 'iphone-15-pro' and status = 1;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_products_cid(cid)`
|
||||
- `idx_ml_products_slug(slug)`
|
||||
|
||||
### 2.4 商品搜索/筛选(按 tags)
|
||||
|
||||
```sql
|
||||
select id, cid, name
|
||||
from public.ml_products
|
||||
where status = 1
|
||||
and tags @> array['手机','苹果']::text[]
|
||||
order by sale_count desc
|
||||
limit 20;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_products_tags using gin(tags)`
|
||||
|
||||
说明:
|
||||
|
||||
- `tags @> array[...]` 是典型的 GIN 可加速模式。
|
||||
|
||||
### 2.5 订单列表(用户维度)
|
||||
|
||||
```sql
|
||||
select id, order_no, total_amount, order_status, created_at
|
||||
from public.ml_orders
|
||||
where user_id = '...user_uuid...'::uuid
|
||||
order by created_at desc
|
||||
limit 20;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_orders_user(user_id, created_at desc)`
|
||||
|
||||
### 2.6 订单列表(商家维度)
|
||||
|
||||
```sql
|
||||
select id, order_no, total_amount, order_status, created_at
|
||||
from public.ml_orders
|
||||
where merchant_id = '...merchant_uuid...'::uuid
|
||||
order by created_at desc
|
||||
limit 20;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_orders_merchant(merchant_id, created_at desc)`
|
||||
|
||||
### 2.7 订单按状态过滤(运营/商家后台常见)
|
||||
|
||||
```sql
|
||||
select id, order_no
|
||||
from public.ml_orders
|
||||
where order_status in (1,2,3)
|
||||
order by created_at desc
|
||||
limit 50;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_orders_status(order_status, created_at desc)`
|
||||
|
||||
### 2.8 购物车加载
|
||||
|
||||
```sql
|
||||
select c.*, s.price, p.name
|
||||
from public.ml_shopping_cart c
|
||||
left join public.ml_product_skus s on s.id = c.sku_id
|
||||
left join public.ml_products p on p.id = c.product_id
|
||||
where c.user_id = '...user_uuid...'::uuid
|
||||
order by c.updated_at desc;
|
||||
```
|
||||
|
||||
依赖索引:
|
||||
|
||||
- `idx_ml_shopping_cart_user(user_id)`
|
||||
|
||||
---
|
||||
|
||||
## 3. JSONB 字段的索引缺口(建议项)
|
||||
|
||||
当前脚本对 `tags` 做了 GIN,但对以下 JSONB 的查询与索引没有“强约束”体现:
|
||||
|
||||
- `ml_orders.shipping_address`
|
||||
- `ml_shops.address/business_hours`
|
||||
- `ml_coupon_templates.applicable_products/categories`
|
||||
|
||||
如果业务上出现以下高频查询:
|
||||
|
||||
- “按城市/区域筛选订单/店铺”
|
||||
- “某个商品可用哪些券”
|
||||
|
||||
建议考虑:
|
||||
|
||||
- 关系化建模(反向关联表)
|
||||
- 或表达式索引(例如对 JSONB 内部字段建索引)
|
||||
|
||||
---
|
||||
|
||||
## 4. 索引维护建议
|
||||
|
||||
- 新增字段/查询前先用 `EXPLAIN (ANALYZE, BUFFERS)` 验证是否命中索引。
|
||||
- 避免为低选择性字段(如 `status` 单列)盲目建索引,优先组合索引匹配真实查询。
|
||||
- 注意 RLS 会影响执行计划与开销,常用过滤字段建议都具备索引(`user_id/merchant_id/status/created_at`)。
|
||||
333
docs/sql/07_business_workflows.md
Normal file
333
docs/sql/07_business_workflows.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# 07 典型业务流程:落表路径与关键字段
|
||||
|
||||
本节用“业务步骤 → 涉及表 → 关键字段/约束/触发器”的方式,把核心链路讲清楚,方便新同事快速理解数据如何流动。
|
||||
|
||||
---
|
||||
|
||||
## 1. 商品发布与上架流程(商家侧)
|
||||
|
||||
### 1.1 创建 SPU(商品主表)
|
||||
|
||||
- **写入表**:`ml_products`
|
||||
- **关键字段**:
|
||||
- `merchant_id`:商家用户(关联 `ak_users.id`)
|
||||
- `category_id/brand_id`
|
||||
- `base_price`
|
||||
- `status`:初始可为草稿(3)或上架(1)
|
||||
- `cid/slug`:对外访问
|
||||
|
||||
示例(简化):
|
||||
|
||||
```sql
|
||||
insert into public.ml_products(
|
||||
merchant_id, category_id, product_code, name, base_price, status
|
||||
)
|
||||
values (
|
||||
'...merchant_uuid...'::uuid,
|
||||
'...category_uuid...'::uuid,
|
||||
'P20260001',
|
||||
'苹果手机',
|
||||
4999.00,
|
||||
3
|
||||
)
|
||||
returning id, cid;
|
||||
```
|
||||
|
||||
### 1.2 定义规格项(可选)
|
||||
|
||||
- **写入表**:`ml_product_specs`
|
||||
- **关键字段**:`spec_name`、`spec_values JSONB`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
insert into public.ml_product_specs(product_id, spec_name, spec_values)
|
||||
values
|
||||
('...product_uuid...'::uuid, '颜色', '["黑","白"]'::jsonb),
|
||||
('...product_uuid...'::uuid, '容量', '["128G","256G"]'::jsonb);
|
||||
```
|
||||
|
||||
### 1.3 创建 SKU(库存与具体价格)
|
||||
|
||||
- **写入表**:`ml_product_skus`
|
||||
- **关键字段**:`specifications JSONB`、`price`、`stock`、`status`
|
||||
- **数据库规则**:
|
||||
- SKU 变更会触发 `update_product_stock()`,自动汇总刷新 `ml_products.total_stock/available_stock`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
insert into public.ml_product_skus(product_id, sku_code, specifications, price, stock)
|
||||
values
|
||||
(
|
||||
'...product_uuid...'::uuid,
|
||||
'SKU-001',
|
||||
'{"颜色":"黑","容量":"128G"}'::jsonb,
|
||||
4999.00,
|
||||
10
|
||||
);
|
||||
|
||||
-- 插入 SKU 后,触发器会把商品 total_stock/available_stock 更新为 10
|
||||
```
|
||||
|
||||
### 1.4 上架商品
|
||||
|
||||
- **更新表**:`ml_products`
|
||||
- **关键字段**:`status = 1`、`published_at`(若使用)
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_products
|
||||
set status = 1, published_at = now()
|
||||
where id = '...product_uuid...'::uuid;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 浏览与收藏(用户侧)
|
||||
|
||||
### 2.1 浏览记录
|
||||
|
||||
- **写入表**:`ml_browse_history`
|
||||
- **约束**:`UNIQUE(user_id, product_id)`
|
||||
- **含义**:倾向记录“最后一次浏览”而不是“浏览流水”。
|
||||
|
||||
典型写法:
|
||||
|
||||
- 插入失败后转更新(upsert)
|
||||
|
||||
```sql
|
||||
insert into public.ml_browse_history(user_id, product_id, browse_duration)
|
||||
values ('...user_uuid...'::uuid, '...product_uuid...'::uuid, 20)
|
||||
on conflict (user_id, product_id)
|
||||
do update set browse_duration = excluded.browse_duration, updated_at = now();
|
||||
```
|
||||
|
||||
### 2.2 收藏
|
||||
|
||||
- **写入表**:`ml_user_favorites`
|
||||
- **约束**:`UNIQUE(user_id, target_type, target_id)`
|
||||
|
||||
```sql
|
||||
insert into public.ml_user_favorites(user_id, target_type, target_id)
|
||||
values ('...user_uuid...'::uuid, 1, '...product_uuid...'::uuid)
|
||||
on conflict do nothing;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 加购与结算(用户侧)
|
||||
|
||||
### 3.1 加入购物车
|
||||
|
||||
- **写入表**:`ml_shopping_cart`
|
||||
- **约束**:`UNIQUE(user_id, product_id, sku_id)`
|
||||
|
||||
常见做法:重复加购时做累加:
|
||||
|
||||
```sql
|
||||
insert into public.ml_shopping_cart(user_id, product_id, sku_id, quantity, selected)
|
||||
values ('...user_uuid...'::uuid, '...product_uuid...'::uuid, '...sku_uuid...'::uuid, 1, true)
|
||||
on conflict (user_id, product_id, sku_id)
|
||||
do update set quantity = public.ml_shopping_cart.quantity + 1, updated_at = now();
|
||||
```
|
||||
|
||||
### 3.2 计算购物车金额
|
||||
|
||||
- **读取函数**:`public.calculate_cart_total(p_user_id)`
|
||||
|
||||
```sql
|
||||
select public.calculate_cart_total('...user_uuid...'::uuid);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 下单(创建订单 + 明细快照)
|
||||
|
||||
### 4.1 订单号生成
|
||||
|
||||
- **函数**:`public.generate_order_no()`(基于 `ml_order_seq`)
|
||||
|
||||
```sql
|
||||
select public.generate_order_no();
|
||||
```
|
||||
|
||||
### 4.2 创建订单主表(地址快照)
|
||||
|
||||
- **写入表**:`ml_orders`
|
||||
- **关键字段**:
|
||||
- `shipping_address JSONB`:下单时把地址“快照化”写进订单
|
||||
- `order_status/payment_status/shipping_status`
|
||||
|
||||
示例(简化):
|
||||
|
||||
```sql
|
||||
insert into public.ml_orders(
|
||||
order_no, user_id, merchant_id,
|
||||
product_amount, discount_amount, shipping_fee, total_amount,
|
||||
shipping_address,
|
||||
order_status, payment_status, shipping_status
|
||||
)
|
||||
values (
|
||||
public.generate_order_no(),
|
||||
'...user_uuid...'::uuid,
|
||||
'...merchant_uuid...'::uuid,
|
||||
4999.00, 0.00, 10.00, 5009.00,
|
||||
'{"receiver":"张三","phone":"138...","province":"广东","city":"深圳","district":"南山","detail":"xxx"}'::jsonb,
|
||||
1, 1, 1
|
||||
)
|
||||
returning id;
|
||||
```
|
||||
|
||||
### 4.3 创建订单明细(商品快照)
|
||||
|
||||
- **写入表**:`ml_order_items`
|
||||
- **关键点**:把 `product_name/sku_name/specifications/image_url/price` 等写入明细,防止商品后改影响历史。
|
||||
|
||||
```sql
|
||||
insert into public.ml_order_items(
|
||||
order_id, product_id, sku_id,
|
||||
product_name, sku_name, specifications, image_url,
|
||||
price, quantity, total_amount
|
||||
)
|
||||
values (
|
||||
'...order_uuid...'::uuid,
|
||||
'...product_uuid...'::uuid,
|
||||
'...sku_uuid...'::uuid,
|
||||
'苹果手机',
|
||||
'黑/128G',
|
||||
'{"颜色":"黑","容量":"128G"}'::jsonb,
|
||||
'https://.../1.png',
|
||||
4999.00,
|
||||
1,
|
||||
4999.00
|
||||
);
|
||||
```
|
||||
|
||||
> 注意:当前可见 SQL 未体现“扣库存/冻结库存”动作,通常需要在同一事务中由应用层或额外 DB 函数完成(详见 `08_data_consistency_boundaries.md`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 支付、发货、完成(状态流转)
|
||||
|
||||
### 5.1 支付完成
|
||||
|
||||
- **更新表**:`ml_orders`
|
||||
- **预期**:`order_status: 1 -> 2`,并写 `paid_amount/payment_status`
|
||||
- **数据库副作用(complete 脚本)**:触发器 `handle_order_status_change()` 自动写 `paid_at`
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 2,
|
||||
payment_status = 2,
|
||||
paid_amount = total_amount
|
||||
where id = '...order_uuid...'::uuid;
|
||||
```
|
||||
|
||||
### 5.2 发货
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 3,
|
||||
shipping_status = 2
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- complete 脚本的触发器会在 2->3 时写 shipped_at
|
||||
```
|
||||
|
||||
### 5.3 收货完成
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 4,
|
||||
shipping_status = 4
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- complete 脚本触发器在 3->4 时写 delivered_at/completed_at,并累计商品 sale_count
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 评价
|
||||
|
||||
- **写入表**:`ml_product_reviews`
|
||||
- **约束设计**:强绑定 `order_id` 与 `order_item_id`,保证评价来自真实订单。
|
||||
|
||||
```sql
|
||||
insert into public.ml_product_reviews(
|
||||
order_id, order_item_id, user_id, product_id, merchant_id,
|
||||
rating, content, images
|
||||
)
|
||||
values (
|
||||
'...order_uuid...'::uuid,
|
||||
'...order_item_uuid...'::uuid,
|
||||
'...user_uuid...'::uuid,
|
||||
'...product_uuid...'::uuid,
|
||||
'...merchant_uuid...'::uuid,
|
||||
5,
|
||||
'很好用',
|
||||
'["https://.../a.png"]'::jsonb
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 优惠券:发放与使用
|
||||
|
||||
### 7.1 领券
|
||||
|
||||
- **写入表**:`ml_user_coupons`
|
||||
- **券码**:可使用 `generate_coupon_code()` 生成
|
||||
|
||||
```sql
|
||||
insert into public.ml_user_coupons(user_id, template_id, coupon_code, status, expire_at)
|
||||
values (
|
||||
'...user_uuid...'::uuid,
|
||||
'...template_uuid...'::uuid,
|
||||
public.generate_coupon_code(),
|
||||
1,
|
||||
now() + interval '30 days'
|
||||
);
|
||||
```
|
||||
|
||||
### 7.2 用券归因
|
||||
|
||||
```sql
|
||||
update public.ml_user_coupons
|
||||
set status = 2,
|
||||
used_at = now(),
|
||||
order_id = '...order_uuid...'::uuid
|
||||
where id = '...user_coupon_uuid...'::uuid
|
||||
and status = 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 订阅:开通/续费/到期
|
||||
|
||||
### 8.1 创建套餐
|
||||
|
||||
- `ml_subscription_plans`
|
||||
|
||||
```sql
|
||||
insert into public.ml_subscription_plans(plan_code, name, price, billing_period)
|
||||
values ('PRO_MONTH', '专业版(月付)', 99.00, 'monthly');
|
||||
```
|
||||
|
||||
### 8.2 用户订阅
|
||||
|
||||
- `ml_user_subscriptions`
|
||||
|
||||
```sql
|
||||
insert into public.ml_user_subscriptions(user_id, plan_id, status, start_date, next_billing_date)
|
||||
values (
|
||||
'...user_uuid...'::uuid,
|
||||
'...plan_uuid...'::uuid,
|
||||
'trial',
|
||||
now(),
|
||||
now() + interval '30 days'
|
||||
);
|
||||
```
|
||||
|
||||
> 说明:订阅模块脚本未与 `ml_orders` 建立外键关联,支付/对账链路通常在应用层或另一个交易子系统实现。
|
||||
116
docs/sql/08_data_consistency_boundaries.md
Normal file
116
docs/sql/08_data_consistency_boundaries.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 08 数据一致性边界:数据库保证什么?应用层还需要保证什么?
|
||||
|
||||
本节的目的:把“责任边界”讲清楚,避免团队误以为数据库已经覆盖了所有一致性问题。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据库层已经显式保证的内容(来自脚本)
|
||||
|
||||
### 1.1 约束(Constraints)保证
|
||||
|
||||
- **枚举合法性**:大量使用 `CHECK (status IN (...))`
|
||||
- 例:`ml_products.status`、`ml_orders.order_status/payment_status/shipping_status`
|
||||
- **唯一性**:
|
||||
- `order_no UNIQUE`
|
||||
- `coupon_code UNIQUE`
|
||||
- `ml_shopping_cart UNIQUE(user_id, product_id, sku_id)`
|
||||
- `ml_shops.merchant_id UNIQUE`(一商家一店)
|
||||
- `ml_delivery_tasks.order_id UNIQUE`(一订单一配送任务)
|
||||
|
||||
### 1.2 触发器保证
|
||||
|
||||
- **`updated_at` 自动维护**:避免应用层漏写
|
||||
- **默认地址唯一**:`ensure_single_default_address()`
|
||||
- **SKU → SPU 库存汇总**:`update_product_stock()`
|
||||
- **订单状态副作用(complete 脚本)**:`handle_order_status_change()` 自动写时间戳,并累计销量
|
||||
|
||||
### 1.3 RLS(行级安全)保证
|
||||
|
||||
- 用户私有数据仅可访问自己的行(档案/地址/购物车/收藏/浏览/券等)
|
||||
- 订单允许买家/卖家访问
|
||||
- 商品公开查询仅限上架
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库层“目前未完全覆盖”的一致性问题(需要显式补齐)
|
||||
|
||||
> 以下并不意味着当前设计不好,而是典型电商系统里必须明确“谁来保证”。
|
||||
|
||||
### 2.1 下单扣库存、防超卖
|
||||
|
||||
脚本可见“库存汇总”,但没有看到:
|
||||
|
||||
- 下单时对 `ml_product_skus.stock` 的原子扣减
|
||||
- 订单取消/超时未支付的库存回补
|
||||
- 库存冻结(预占)机制
|
||||
|
||||
**风险**:并发下单可能超卖。
|
||||
|
||||
**建议补齐方案**(按复杂度递增):
|
||||
|
||||
- **方案 A(最小补齐)**:提供一个原子扣减函数
|
||||
- `update ml_product_skus set stock = stock - :qty where id=:sku_id and stock >= :qty;`
|
||||
- 受影响行数为 1 表示扣减成功,否则失败
|
||||
- **方案 B(常见电商)**:引入“冻结库存”
|
||||
- SKU 增加 `reserved_stock` 或独立冻结表
|
||||
- 下单冻结、支付确认扣减、取消释放
|
||||
- **方案 C(审计与对账)**:引入库存流水
|
||||
- 每次扣减/回补记录流水,便于审计
|
||||
|
||||
### 2.2 支付对账与幂等
|
||||
|
||||
脚本中订单有 `payment_status/paid_amount`,但未见:
|
||||
|
||||
- 支付流水表(第三方支付回调存证)
|
||||
- 支付回调幂等键
|
||||
- 退款流水/退款幂等
|
||||
|
||||
**建议**:补交易流水表(或在应用层引入专门支付子系统)并明确幂等策略。
|
||||
|
||||
### 2.3 优惠券核销一致性
|
||||
|
||||
当前有 `ml_user_coupons.status/used_at/order_id`,但未见:
|
||||
|
||||
- “同一张券只能用一次”的强事务保证(除非应用层做 CAS 更新)
|
||||
- 与订单金额计算的强一致校验
|
||||
|
||||
建议:
|
||||
|
||||
- 用条件更新核销:`where status=1` 确保并发只成功一次
|
||||
- 关键核销与订单创建在同一事务内
|
||||
|
||||
### 2.4 统计字段回滚
|
||||
|
||||
脚本在订单完成时累计 `sale_count`,但未看到:
|
||||
|
||||
- 退款/取消是否回滚 `sale_count`
|
||||
|
||||
建议:
|
||||
|
||||
- 明确统计字段口径:
|
||||
- `sale_count` 是“累计成交量”还是“累计下单量”
|
||||
- 若需可回滚:要补对应触发器/作业,或使用流水聚合。
|
||||
|
||||
---
|
||||
|
||||
## 3. 建议的边界划分(团队共识)
|
||||
|
||||
- **数据库层**:
|
||||
- 基础合法性(约束/唯一性)
|
||||
- 关键自动维护字段(updated_at、默认地址唯一、库存汇总、SEO 等)
|
||||
- 访问控制(RLS)
|
||||
- **应用层**:
|
||||
- 复杂事务(下单扣库存、支付幂等、退款)
|
||||
- 业务规则组合(优惠叠加、分摊、拆单、风控)
|
||||
- 跨域协调(订阅与订单的统一计费/对账)
|
||||
|
||||
---
|
||||
|
||||
## 4. 推荐补充的“最小一致性清单”(可用于评审)
|
||||
|
||||
- 下单扣减库存是否原子?
|
||||
- 未支付关闭订单是否回补库存?
|
||||
- 支付回调是否幂等?
|
||||
- 退款回调是否幂等?
|
||||
- 优惠券核销是否并发安全?
|
||||
- 统计字段口径是否明确、是否需要回滚?
|
||||
126
docs/sql/09_migrations_and_versions.md
Normal file
126
docs/sql/09_migrations_and_versions.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 09 迁移与版本策略(complete vs migration vs tests)
|
||||
|
||||
本节解释仓库中与商城数据库相关的脚本如何分工、适合在什么场景使用,以及推荐的执行顺序。
|
||||
|
||||
---
|
||||
|
||||
## 1. 你现在有哪几类 SQL
|
||||
|
||||
### 1.1 “完整初始化脚本”(偏一次性创建)
|
||||
|
||||
- `doc_mall/database/complete_mall_database.sql`
|
||||
|
||||
特点:
|
||||
|
||||
- 覆盖范围大:表结构、索引、触发器、函数、视图、RLS 策略、初始化数据、SEO 函数等。
|
||||
- 更像“新环境一键搭建”的脚本。
|
||||
|
||||
风险点:
|
||||
|
||||
- 若在已有环境重复执行,可能因为对象已存在/差异存在导致失败(脚本虽有部分 `IF NOT EXISTS`,但不是全幂等)。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 新环境快速搭建
|
||||
- 演示/验证/PoC
|
||||
|
||||
### 1.2 “迁移脚本”(偏幂等/可增量)
|
||||
|
||||
- `doc_mall/database/mall_migration.sql`
|
||||
|
||||
特点:
|
||||
|
||||
- 大量使用 `CREATE TABLE IF NOT EXISTS`、`CREATE INDEX IF NOT EXISTS`。
|
||||
- 触发器创建使用 `DO $$ ... IF NOT EXISTS` 的方式避免重复创建报错。
|
||||
- 插入初始化数据使用 `ON CONFLICT DO NOTHING`。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 在已有数据库上“补齐商城模块”
|
||||
- 生产环境更安全的增量迁移
|
||||
|
||||
### 1.3 “升级/差异修复脚本”(按数据库现状推荐执行)
|
||||
|
||||
从 `mall_sql/tests/mall_database_check.sql` 可看出项目侧存在“根据现状推荐脚本”的思路,提到:
|
||||
|
||||
- `mall_alter_upgrade.sql`:完整升级(表 + 字段 + 索引 + 函数)
|
||||
- `mall_fields_only_upgrade.sql`:仅字段升级(最小化修改)
|
||||
- `mall_migration.sql`:完整建表(全新部署/缺表时)
|
||||
- `mall_seo_security.sql`:SEO 优化与安全策略
|
||||
|
||||
> 这些脚本在 `doc_mall/database/` 与 `mall_sql/migrations/` 中都有对应版本(具体以仓库实际文件为准)。
|
||||
|
||||
### 1.4 “检查/测试脚本”(偏验收与自检)
|
||||
|
||||
- `mall_sql/tests/mall_database_check.sql`
|
||||
- `mall_sql/tests/validation_test.sql`
|
||||
- `mall_sql/tests/mock_data_insert.sql`
|
||||
- `mall_sql/tests/verify_mock_data_fix.sql`
|
||||
- `mall_sql/tests/create_supabase_auth_users.sql`
|
||||
|
||||
适用场景:
|
||||
|
||||
- 验收环境是否缺表/缺字段/缺索引
|
||||
- 生成建议与 ALTER 语句
|
||||
- 插入 mock 数据用于联调
|
||||
|
||||
---
|
||||
|
||||
## 2. 推荐执行顺序(生产/测试通用)
|
||||
|
||||
### 2.1 新环境(从 0 到可用)
|
||||
|
||||
推荐路径(更稳妥):
|
||||
|
||||
1. 执行 `mall_migration.sql`(幂等建表 + 基础索引 + 关键函数/触发器)
|
||||
2. 执行 SEO 与安全策略脚本(如 `mall_seo_security.sql`,若 migration 未覆盖)
|
||||
3. 执行订阅模块脚本(如需要):`create_mall_subscription_tables.sql`
|
||||
4. 执行检查脚本:`mall_database_check.sql`
|
||||
5.(测试环境)执行 mock 数据脚本:`mock_data_insert.sql`
|
||||
|
||||
替代路径(快速但风险更高):
|
||||
|
||||
- 直接执行 `complete_mall_database.sql` 一次性完成(适合演示/PoC)。
|
||||
|
||||
### 2.2 已有环境(补齐缺失)
|
||||
|
||||
1. 先执行检查脚本:`mall_database_check.sql`
|
||||
2. 根据输出建议选择:
|
||||
- 缺字段多 + 缺表:`mall_alter_upgrade.sql`
|
||||
- 只缺字段:`mall_fields_only_upgrade.sql`
|
||||
- 只缺表:`mall_migration.sql`
|
||||
3. 再执行 SEO/RLS 策略脚本(如缺失)
|
||||
4. 再跑一次检查脚本确认
|
||||
|
||||
---
|
||||
|
||||
## 3. 版本差异与兼容性注意点
|
||||
|
||||
### 3.1 订单状态口径差异
|
||||
|
||||
在不同脚本中对 `order_status = 5` 的文字描述存在差异(“取消/取货”)。
|
||||
|
||||
建议:
|
||||
|
||||
- 在应用层/文档中统一口径
|
||||
- 如需扩展状态,务必迁移更新 `CHECK` 约束与相关触发器逻辑
|
||||
|
||||
### 3.2 用户角色字段差异:`ak_users.role` vs `ml_user_profiles.user_type`
|
||||
|
||||
- `mall_migration.sql`:在 `ml_user_profiles` 引入 `user_type`
|
||||
- `complete_mall_database.sql`:更多使用 `ak_users.role`(并在视图里做 role_name 映射)
|
||||
|
||||
建议:
|
||||
|
||||
- 明确项目最终“权威字段”是哪一个
|
||||
- 避免两个口径长期并存造成权限与业务判断分裂
|
||||
|
||||
---
|
||||
|
||||
## 4. 推荐的团队规范
|
||||
|
||||
- 生产环境:优先使用“幂等迁移脚本 + 小步变更”,避免一次性大脚本覆盖执行。
|
||||
- 每次变更:
|
||||
- 同步更新检查脚本/验收项
|
||||
- 明确回滚策略(尤其是约束、枚举、数据迁移)
|
||||
- 对外接口依赖的 `cid/slug`:变更需谨慎(可能影响 SEO 与路由)。
|
||||
91
docs/sql/10_quality_checks.md
Normal file
91
docs/sql/10_quality_checks.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 10 质量自检与验收(表/字段/索引/扩展/函数)
|
||||
|
||||
本节汇总仓库中已有的“数据库状态检查/验证脚本”,并给出在交付与升级时推荐的自检流程。
|
||||
|
||||
---
|
||||
|
||||
## 1. 现有自检脚本清单
|
||||
|
||||
在 `mall_sql/tests/` 目录下可见:
|
||||
|
||||
- `mall_database_check.sql`
|
||||
- 检查 `ak_users` 是否缺少商城字段
|
||||
- 检查商城核心表是否存在
|
||||
- 检查关键索引是否存在
|
||||
- 检查必要扩展是否安装
|
||||
- 检查关键函数是否存在
|
||||
- 输出“推荐执行方案”(fields_only / alter_upgrade / migration / seo_security 等)
|
||||
- 可生成具体 `ALTER TABLE` 建议
|
||||
|
||||
- `validation_test.sql`(存在即表示有更细校验,建议结合实际内容补充到本文档)
|
||||
- `mock_data_insert.sql` / `verify_mock_data_fix.sql`
|
||||
- 用于联调/验证查询与页面
|
||||
- `create_supabase_auth_users.sql`
|
||||
- 可能用于创建 Supabase auth 用户或测试映射
|
||||
|
||||
---
|
||||
|
||||
## 2. 推荐自检流程(交付/升级)
|
||||
|
||||
### 2.1 升级前(评估现状)
|
||||
|
||||
1. 执行 `mall_database_check.sql`
|
||||
2. 根据输出信息确认:
|
||||
- `ak_users` 缺哪些字段
|
||||
- `ml_` 核心表缺哪些
|
||||
- 关键索引是否齐全
|
||||
- 扩展是否安装
|
||||
- 函数是否缺失
|
||||
|
||||
### 2.2 执行升级/迁移
|
||||
|
||||
根据 `mall_database_check.sql` 给出的推荐:
|
||||
|
||||
- 缺字段多 + 缺表:`mall_alter_upgrade.sql`
|
||||
- 只缺字段:`mall_fields_only_upgrade.sql`
|
||||
- 只缺表:`mall_migration.sql`
|
||||
- SEO/RLS 缺失:`mall_seo_security.sql`
|
||||
|
||||
### 2.3 升级后(验证验收)
|
||||
|
||||
1. 再次执行 `mall_database_check.sql`,确认缺失项归零
|
||||
2.(测试环境)插入 mock 数据(若有脚本)
|
||||
3. 跑核心链路的“最小验收用例”(见下)
|
||||
|
||||
---
|
||||
|
||||
## 3. 最小验收用例(建议团队固化)
|
||||
|
||||
### 3.1 用户侧
|
||||
|
||||
- 能创建/更新 `ml_user_profiles`
|
||||
- 能新增地址并设置默认,确认默认地址唯一
|
||||
- 能加购、更新购物车数量
|
||||
|
||||
### 3.2 商品侧
|
||||
|
||||
- 能创建商品 + SKU
|
||||
- 更新 SKU 库存,确认 `ml_products.total_stock/available_stock` 自动刷新
|
||||
- 商品上架后,普通用户侧可查询到(RLS/策略允许)
|
||||
|
||||
### 3.3 订单侧
|
||||
|
||||
- 能创建订单 + 明细
|
||||
- 更新订单状态(1->2->3->4),确认时间戳与销量累计逻辑(若使用 complete 脚本触发器)
|
||||
|
||||
### 3.4 营销/优惠券
|
||||
|
||||
- 创建券模板
|
||||
- 领券生成券码
|
||||
- 核销券并绑定订单
|
||||
|
||||
---
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
- 生产执行前务必在测试环境验证脚本兼容性与执行顺序。
|
||||
- 若启用 RLS,检查脚本/运维脚本需要使用 service role 或具有相应权限的连接方式,否则可能误判数据可见性。
|
||||
- 如新增状态枚举值,需同步更新:
|
||||
- `CHECK` 约束
|
||||
- 相关触发器/函数
|
||||
- 文档字典(`03_enums_status_dict.md`)
|
||||
184
docs/sql/11_roles_and_permissions_strategy.md
Normal file
184
docs/sql/11_roles_and_permissions_strategy.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 11 角色、权限与路由整合策略
|
||||
|
||||
本节提供一套完整的“角色定义 → RLS 策略 → 前端路由/跳转 → 业务流程”整合方案,旨在将数据库安全模型与项目实际开发无缝结合。
|
||||
|
||||
---
|
||||
|
||||
## 1. 角色定义(权威口径)
|
||||
|
||||
为避免权限判断分裂,项目应统一使用 `public.ak_users.role` 作为唯一权威的角色字段。
|
||||
|
||||
### 1.1 推荐的角色枚举
|
||||
|
||||
- `customer`:消费者
|
||||
- `merchant`:商家
|
||||
- `delivery`:配送员
|
||||
- `service`:客服
|
||||
- `admin`:平台管理员
|
||||
- `analytics`:数据分析/运营角色
|
||||
|
||||
> **决策点**:
|
||||
> - `analytics` 角色是可选的。如果运营/分析师与 `admin` 权限边界不清,可以先统一为 `admin`。
|
||||
> - 但长远看,为“数据查看者”设定独立角色有利于最小权限原则。
|
||||
|
||||
### 1.2 如何在数据库中获取当前用户角色
|
||||
|
||||
通常通过一个函数实现,该函数内部使用 `auth.uid()`。
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION public.get_current_user_role()
|
||||
RETURNS TEXT
|
||||
LANGUAGE sql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT role FROM public.ak_users WHERE auth_id = auth.uid() LIMIT 1;
|
||||
$$;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. RLS 策略与权限设计
|
||||
|
||||
### 2.1 权限分层(推荐)
|
||||
|
||||
- **A. Row Owner(行归属者)**
|
||||
- 用户只能访问自己的数据,如地址、购物车、收藏、个人订单。
|
||||
- RLS 策略核心:`auth.uid() = (SELECT auth_id FROM ak_users WHERE id = <row.user_id>)`
|
||||
|
||||
- **B. Business Owner(业务归属者)**
|
||||
- 商家只能访问自己店铺的数据,如商品、店铺订单。
|
||||
- RLS 策略核心:`auth.uid() = (SELECT auth_id FROM ak_users WHERE id = <row.merchant_id>)`
|
||||
|
||||
- **C. Privileged(特权角色)**
|
||||
- `admin/analytics` 角色需要访问全局数据,尤其是聚合统计。
|
||||
- **强烈建议**:不要为这些角色直接开放表的全局 `SELECT` 权限。
|
||||
|
||||
### 2.2 如何让 `admin/analytics` 安全地看全局数据?
|
||||
|
||||
**推荐方案:RPC + `SECURITY DEFINER`**
|
||||
|
||||
1. **维持表的严格 RLS**:确保 `customer/merchant` 无法越权。
|
||||
2. **Analytics 页面只调用 RPC**:例如 `rpc_analytics_*` 系列函数。
|
||||
3. **RPC 函数必须 `SECURITY DEFINER`**:使其以“函数所有者”(通常是 `postgres` 超级用户)的权限执行,从而绕过调用者的 RLS 限制。
|
||||
4. **RPC 函数内部必须做显式鉴权**:这是安全闭环的关键。
|
||||
|
||||
**RPC 鉴权模板**:
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION public.rpc_analytics_sales_kpis(
|
||||
p_start_date DATE,
|
||||
p_end_date DATE
|
||||
)
|
||||
RETURNS TABLE (...)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER -- 以函数所有者权限执行
|
||||
SET search_path = public -- 显式设置 search_path,避免 search_path 攻击
|
||||
AS $$
|
||||
BEGIN
|
||||
-- 1. 在函数入口处做权限检查
|
||||
IF get_current_user_role() NOT IN ('admin', 'analytics') THEN
|
||||
RAISE EXCEPTION 'Permission denied: required role admin or analytics';
|
||||
END IF;
|
||||
|
||||
-- 2. 执行统计(因为是 SECURITY DEFINER,这里可以查到所有数据)
|
||||
RETURN QUERY
|
||||
WITH ...
|
||||
-- ... 统计逻辑 ...
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
> **现状风险**:当前 `rpc_analytics_*` 脚本未包含 `SECURITY DEFINER` 与内部鉴权。若直接部署,当 RLS 开启时,`admin/analytics` 调用会因权限不足而查不到数据。
|
||||
|
||||
---
|
||||
|
||||
## 3. 前端项目整合:路由守卫与业务流程
|
||||
|
||||
### 3.1 路由分组(按角色)
|
||||
|
||||
项目页面按角色划分,便于集中管理路由与权限。
|
||||
|
||||
- `/pages/mall/consumer/**`
|
||||
- `/pages/mall/merchant/**`
|
||||
- `/pages/mall/delivery/**`
|
||||
- `/pages/mall/admin/**`
|
||||
- `/pages/mall/analytics/**`
|
||||
|
||||
### 3.2 路由守卫(客户端鉴权)
|
||||
|
||||
在 `services/analytics/authGuard.uts`(或类似文件)中,应提供更精细的守卫函数。
|
||||
|
||||
**守卫函数建议**:
|
||||
|
||||
```typescript
|
||||
// services/auth/guard.uts (示例)
|
||||
import { getCurrentUser } from './user.uts' // 假设此函数能获取当前登录用户及其角色
|
||||
|
||||
// 1. 确保已登录
|
||||
export function ensureLoggedIn(options: { redirect?: string } = {}): boolean {
|
||||
const user = getCurrentUser();
|
||||
if (!user) {
|
||||
uni.navigateTo({ url: options.redirect ?? '/pages/user/login' });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. 确保具备指定角色之一
|
||||
export function ensureRole(allowedRoles: Array<string>, options: { toastTitle?: string } = {}): boolean {
|
||||
if (!ensureLoggedIn()) return false;
|
||||
|
||||
const user = getCurrentUser();
|
||||
if (!user || !allowedRoles.includes(user.role)) {
|
||||
uni.showToast({ title: options.toastTitle ?? '无权访问', icon: 'none' });
|
||||
// 可选择返回上一页或跳转首页
|
||||
setTimeout(() => uni.navigateBack(), 1500);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**在 Analytics 页面中使用**:
|
||||
|
||||
```typescript
|
||||
// pages/mall/analytics/index.uvue
|
||||
onLoad(() => {
|
||||
if (!ensureRole(['admin', 'analytics'], { toastTitle: '仅管理员可访问数据分析' })) {
|
||||
return;
|
||||
}
|
||||
initDashboard();
|
||||
});
|
||||
```
|
||||
|
||||
### 3.3 业务流程闭环(以 Analytics 首页为例)
|
||||
|
||||
1. **用户访问** `/pages/mall/analytics/index`。
|
||||
2. **前端守卫**:`onLoad` 中 `ensureRole(['admin', 'analytics'])` 执行:
|
||||
- 未登录 → 跳转登录页
|
||||
- 已登录但角色不符 → toast 提示 + 返回
|
||||
3. **调用 Service**:`dashboardService.uts` 的 `fetch...` 函数被调用。
|
||||
4. **执行 RPC**:`rpcOrNull('rpc_analytics_sales_kpis', ...)` 发起请求。
|
||||
5. **数据库鉴权**:`rpc_analytics_sales_kpis` 函数内部首先检查 `get_current_user_role()` 是否为 `admin/analytics`。
|
||||
- 权限不足 → `RAISE EXCEPTION`,前端收到错误。
|
||||
- 权限通过 → 执行统计。
|
||||
6. **数据返回**:前端拿到聚合数据并渲染。
|
||||
|
||||
这个流程实现了“前端快速失败 + 后端强制校验”的安全闭环。
|
||||
|
||||
---
|
||||
|
||||
## 4. 权限矩阵(总结)
|
||||
|
||||
| 角色 | `customer` | `merchant` | `admin/analytics` |
|
||||
| -------- | -------------------------------------------- | ---------------------------- | -------------------------------------- |
|
||||
| **可读** | 上架商品、自己的(订单/地址/购物车/收藏/券) | 自己的(商品/订单/店铺数据) | 全局聚合数据(通过 RPC) |
|
||||
| **可写** | 自己的(地址/购物车/收藏/订单创建) | 自己的(商品/发货/售后) | 通常不直接写业务表(通过后台管理功能) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 待办与实现建议
|
||||
|
||||
1. **统一角色字段**:在项目中明确 `ak_users.role` 为唯一权威,并提供获取当前用户角色的函数。
|
||||
2. **增强 RPC 安全性**:为所有 `rpc_analytics_*` 函数增加 `SECURITY DEFINER` 与内部权限检查。
|
||||
3. **实现前端路由守卫**:创建 `ensureRole` 函数,并在所有 `analytics` 子包页面中统一调用。
|
||||
17
docs/sql/README.md
Normal file
17
docs/sql/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# SQL 文档目录(商城)
|
||||
|
||||
本目录用于存放商城数据库(`doc_mall` / `mall_sql`)的**详尽说明文档**,由 `docs/sql_summary.md` 作为入口索引。
|
||||
|
||||
## 文档列表
|
||||
|
||||
- `00_overview.md`
|
||||
- `01_tables_catalog.md`
|
||||
- `02_relationships_er.md`
|
||||
- `03_enums_status_dict.md`
|
||||
- `04_triggers_and_functions.md`
|
||||
- `05_rls_permissions_matrix.md`
|
||||
- `06_indexes_and_query_patterns.md`
|
||||
- `07_business_workflows.md`
|
||||
- `08_data_consistency_boundaries.md`
|
||||
- `09_migrations_and_versions.md`
|
||||
- `10_quality_checks.md`
|
||||
Reference in New Issue
Block a user