Files
medical-mall/BACKEND_MIGRATION_PLAN.md
2026-03-12 18:05:32 +08:00

7.1 KiB
Raw Blame History

后端收敛与防护设计(最小可落地方案)

说明:本文档针对当前仓库中前端直接写 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_KEYservice_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 JWTuid 作为操作人。
  • 内部调用:后端使用环境变量 SERVICE_ROLE_KEY 或直接调用 RPC推荐执行受权写入。
  • 返回格式统一并带错误码,便于前端处理与监控。

C. Postgres RPC示例 SQL

下面为 rpc_accept_task 草稿,供 DB 管理员在 Dev 环境调整与测试:

-- 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;

  -- 执行原子更新:仅当 task 可被领取status=1 且 driver_id IS NULL
  WITH u AS (
    UPDATE ml_delivery_tasks
    SET driver_id = p_driver_uuid, status = 2, updated_at = now()
    WHERE id = p_task_uuid AND status = 1 AND driver_id IS NULL
    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_delivery_tasks', 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
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_delivery_tasksml_orders
    • 禁止直接由匿名或普通前端更新关键列(例如 driver_id、status只允许 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_roleak/config.uts(紧急)
  • server/ 新增 POST /api/v1/delivery/accept-task(鉴权 + 调用 RPC
  • 在 DB 创建 rpc_accept_taskaction_dedupeaudit_logs
  • user_addressesml_delivery_tasksml_orders 等启用 RLS 策略(逐表)
  • 前端将接单/确认调用迁移到新 APIfeature-flag并回归测试
  • 部署后监控并逐步扩大灰度

风险与注意事项

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

推荐下一步(我可以马上帮你做)

  • 选项 1生成并提交 ak/config.uts 的 patchSUPA_KEY 替换为示例 anon阻断风险
  • 选项 2生成 rpc_accept_task 的完整 SQL + server/routes/delivery.js 的 Node 实现样例(包含鉴权中间件与调用示例)。
  • 选项 3列出仓库内所有前端写入点并按“必须走后端 / 可保留直写”分级清单(便于逐步迁移)。

请回复 123 选项,或告诉我需要调整的文档内容/格式。