# 后端收敛与防护设计（最小可落地方案）

说明：本文档针对当前仓库中前端直接写 Supabase（存在风险：前端使用 service_role key）问题，提供可执行的最小后台设计、接口与 DB 改造清单，供开发/运维快速落地。

---

## 目标（Why）
- 阻断前端持有 `service_role` 导致的越权写库风险。
- 将关键业务写入（接单/订单状态/资金/库存等）收敛到可信后端，保证原子性、幂等和审计。
- 在数据库侧加入最后一道防线（约束 + RLS + RPC）。

---

## 概要执行步骤（What / High level）
1. 立刻移除前端的 `service_role`，前端只用 `anon`（阻断最大风险）。
2. 实现最小后台 API（订单管理、核心状态流转 rpc + HTTP 接口）。
3. 在数据库添加约束、RLS 策略与 RPC（把关键状态流转做成原子函数）。
4. 前端分阶段切换到新 API 并回归测试。
5. 补充审计、幂等与监控，逐步迁移其它敏感写入。

---

## 详细步骤（可直接执行）

### A. 紧急措施（立即）
- 编辑 `ak/config.uts`：注释或删除明文 `SUPA_KEY`（service_role），替为 anon 或从构建环境注入。
- 确认前端不再把 service_role 打包发布（CI/构建流水线更新）。

### B. 最小后端 API（优先交付）
在 `server/` 下新增 `server/routes/delivery.js`（或 Fastify 插件）。

推荐接口（统一返回 `{ ok, code?, message?, data? }`）：
- POST /api/v1/delivery/accept-task
  - body: { task_id, action_id?: string }
  - auth: Bearer user_jwt
  - 后端流程：验证 token → 验证司机权限 → 调用 RPC `rpc_accept_task(uid, task_id, action_id)` → 返回 task 新状态
- POST /api/v1/delivery/update-status
  - body: { task_id, new_status, action_id, metadata? }
  - 后端流程：验证 → 调用 `rpc_update_delivery_status(...)`（RPC 内部做多表事务）
- POST /api/v1/notifications/express/create
  - body: notification payload + message_id/action_id（幂等）
  - 后端流程：幂等检查 -> 插入 -> 触发 push（内部队列）

后端实现要点：
- 鉴权：解析并验证前端 Supabase JWT，取 `uid` 作为操作人。
- 内部调用：后端使用环境变量 `SERVICE_ROLE_KEY` 或直接调用 RPC（推荐）执行受权写入。
- 返回格式统一并带错误码，便于前端处理与监控。

### C. Postgres RPC（示例 SQL）
下面为 `rpc_accept_task` 草稿，供 DB 管理员在 Dev 环境调整与测试：

```sql
-- rpc_accept_task: driver 领取任务（幂等 + 原子）
CREATE OR REPLACE FUNCTION public.rpc_accept_task(p_driver_uuid uuid, p_task_uuid uuid, p_action_id text)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
  v_task RECORD;
BEGIN
  -- 幂等：插入到去重表，若已存在则返回已处理结果
  INSERT INTO action_dedupe(action_id, created_at)
  VALUES (p_action_id, now())
  ON CONFLICT (action_id) DO NOTHING;

  -- 执行原子更新
  WITH u AS (
    UPDATE ml_orders
    SET status = 2, updated_at = now()
    WHERE id = p_task_uuid AND status = 1
    RETURNING *
  )
  SELECT * INTO v_task FROM u LIMIT 1;

  IF NOT FOUND THEN
    RETURN jsonb_build_object('ok', false, 'message', 'task not available');
  END IF;

  -- 写审计
  INSERT INTO audit_logs(actor_id, action, target_table, target_id, payload, created_at)
  VALUES (p_driver_uuid, 'accept_task', 'ml_orders', p_task_uuid, row_to_json(v_task), now());

  RETURN jsonb_build_object('ok', true, 'task', to_jsonb(v_task));
END;
$$;
```

注意：
- 使用 `SECURITY DEFINER` 并确保函数拥有适当权限（仅 server/service_role 可调用）。
- `action_dedupe` 表需创建并设 unique(action_id)。

### D. RLS 与 约束（示例）
- 开启 RLS，例如对 `user_addresses`：
```sql
ALTER TABLE user_addresses ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_owns_address ON user_addresses
  FOR ALL
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);
```
- 对关键表（如 `ml_orders`）：
  - 禁止直接由匿名或普通前端更新关键列；只允许 `rpc` / service 调用通过函数更新。
- 索引/约束：
  - message_id 唯一：`CREATE UNIQUE INDEX ux_express_notifications_message_id ON express_notifications(message_id);`
  - 补全 NOT NULL / FK / CHECK（枚举字段限制等）。

### E. 鉴权实现要点（后端）
- 验证前端 JWT：解析/校验 Supabase JWT（可使用 Supabase 的用户 API 或直接 JWT 验证）。从 token 获取 `uid` 作为操作人。
- 后端自身使用环境变量 `SERVICE_ROLE_KEY` 调用 Supabase Admin API 或直接调用 Postgres RPC（建议后者更原子）。
- 不要在后端把 `service_role` 返回或写进前端文件。

### F. 幂等与去重
- 对所有会被重复调用的外部动作（webhook、client retry）要求 `action_id`/`message_id`，并在 DB 层做 `ON CONFLICT DO NOTHING` 或在 RPC 先检查 `action_dedupe`。

### G. 审计与监控
- 新建 `audit_logs(actor_id, action, target_table, target_id, payload, created_at)` 并在 RPC 中写入。
- 错误报警（Sentry 或日志轮询），并在关键接口记录 metrics（错误率、latency、冲突数）。

---

## 迁移与上线路线（分阶段）
1. Dev：实现 RPC + API，更新 dev 配置把前端用 anon，完成单元测试与集成测试。
2. Stage：灰度发布后端 API，部分司机/少量流量切换到 API。监控 48h。
3. Prod：全面切换，删除前端中的直写调用（或通过 feature-flag 关闭）。
4. 回滚：保留旧直写代码并用 feature flag 随时回退；若回退需同时短期恢复前端原 key（仅极端应急，注意风险并在短时间内移除）。

---

## 最小交付清单（可追踪任务）
- [ ] 注释/移除前端 `service_role`：`ak/config.uts`（紧急）
- [ ] 在 `server/` 新增 `POST /api/v1/delivery/accept-task`（鉴权 + 调用 RPC）
- [ ] 在 DB 创建 `rpc_accept_task`、`action_dedupe`、`audit_logs` 表
- [ ] 对 `user_addresses`、`ml_orders` 等启用 RLS 策略（逐表）
- [ ] 前端将接单/确认调用迁移到新 API（feature-flag）并回归测试
- [ ] 部署后监控并逐步扩大灰度

---

## 风险与注意事项
- 风险：短期内若不移除 `service_role`，即便实现后台 API，也无法阻止攻击者通过已有 key 直接写库。优先级最高。
- 注意隐私/合规：审计日志要避免写入敏感明文（例如完整支付凭证）或加密存储。
- DB 权限：确保 RPC 使用受限角色执行并限制 RPC 所能做的动作。

---

## 推荐下一步（我可以马上帮你做）
- 选项 1：生成并提交 `ak/config.uts` 的 patch，将 `SUPA_KEY` 替换为示例 anon（阻断风险）。
- 选项 2：生成 `rpc_accept_task` 的完整 SQL + `server/routes/delivery.js` 的 Node 实现样例（包含鉴权中间件与调用示例）。
- 选项 3：列出仓库内所有前端写入点并按“必须走后端 / 可保留直写”分级清单（便于逐步迁移）。

请回复 `1`、`2` 或 `3` 选项，或告诉我需要调整的文档内容／格式。

---
## 附录：前端不得直接连接数据库（简明实施指南）

结论：所有需要提升权限、跨表原子性、审计或幂等保证的写入必须走可信后端；仅在严格受限（RLS + 约束）且只影响用户自身资源时，前端可用 anon key 直写。

- 何时必须走后端：更新 `driver_id`、订单状态、资金/结算、库存变更、跨表事务、需要审计或幂等的操作、需使用 `service_role` 权限的写入。
- 何时可允许前端直写：仅用户自身数据（例如 `user_addresses`）且已启用 RLS 与完整约束，且不涉及跨表或审计要求。

最小后端职责（示例）：
- 验证前端 JWT（取 `uid`）；做权限校验与幂等检查（`action_id`）。
- 调用 Postgres RPC（`SECURITY DEFINER`）或使用 `SERVICE_ROLE_KEY` 完成受权写入。
- 写入 `audit_logs` 并返回统一错误格式 `{ ok, code?, message?, data? }`。

紧急建议（复述以便执行）：
- 立即移除或注释前端的 `service_role`（已在工作区修改 `ak/config.uts`）。
- 在 CI 中阻止带有 `service_role` 的 key 进入前端配置或打包产物。
- 为关键流实现 RPC（如 `rpc_accept_task`）并暴露最小后端 API（如 `/api/v1/delivery/accept-task`），逐步将前端写入迁移到后端。

需要我把这个附录再整理为单独文件或把 `rpc_accept_task` SQL 与 `server/routes/delivery.js` 示例直接追加到本文件吗？
