# 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` 子包页面中统一调用。
