# 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 = )` - **B. Business Owner(业务归属者)** - 商家只能访问自己店铺的数据,如商品、店铺订单。 - RLS 策略核心:`auth.uid() = (SELECT auth_id FROM ak_users WHERE 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, 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` 子包页面中统一调用。