Files
medical-mall/BACKEND_MIGRATION_PLAN.md
2026-03-16 14:58:00 +08:00

173 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端收敛与防护设计(最小可落地方案)
说明:本文档针对当前仓库中前端直接写 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;
-- 执行原子更新:仅当 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`
```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_delivery_tasks``ml_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_role``ak/config.uts`(紧急)
- [ ]`server/` 新增 `POST /api/v1/delivery/accept-task`(鉴权 + 调用 RPC
- [ ] 在 DB 创建 `rpc_accept_task``action_dedupe``audit_logs`
- [ ]`user_addresses``ml_delivery_tasks``ml_orders` 等启用 RLS 策略(逐表)
- [ ] 前端将接单/确认调用迁移到新 APIfeature-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` 示例直接追加到本文件吗?