Files
gongxue-base/docs/refactor/supabase-frontend-access-strategy.md
2026-06-28 21:00:55 +08:00

177 lines
7.7 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 前端访问策略
更新时间2026-06-28
这份文档用于回答一个关键架构问题Taro/H5/小程序前端到底应该直接调用 Supabase还是调用我们自己的 `apps/api` 后端?
结论:采用 Supabase-first 的混合架构。优先使用 Supabase Auth、RLS、视图、RPC、Storage 等原生能力涉及密钥、跨表事务、支付、导入、对象存储签名、CRM、AI 和复杂权限的业务命令,放到 Edge Functions、`apps/api` 或 worker。
## 官方依据
Supabase 官方文档的核心原则:
- 前端可以使用 Supabase client/Data API 访问数据,但前提是启用 RLS并且只授予最小权限。
- https://supabase.com/docs/guides/database/secure-data
- https://supabase.com/docs/guides/database/postgres/row-level-security
- 前端应使用 publishable keypublishable key 可以暴露,但必须配合 RLS 和最小权限。
- https://supabase.com/docs/guides/database/secure-data
- https://supabase.com/docs/guides/getting-started/api-keys
- secret key / service role key 永远不能暴露在前端,因为它们会绕过 RLS。
- https://supabase.com/docs/guides/getting-started/api-keys
- 复杂服务端逻辑、第三方 API、webhook、密钥和数据库连接应放在服务端例如 Edge Functions、worker 或自有后端。
- https://supabase.com/docs/guides/functions
- Supabase Auth 使用 JWTJWT 可以和 RLS、服务端鉴权结合。
- https://supabase.com/docs/guides/auth
- https://supabase.com/docs/guides/auth/jwts
## 对本项目的判断
我们的系统不是简单 CRUD 应用,而是面向同行销售的题库 SaaS
- 多租户隔离。
- 平台超级管理员和租户管理员双后台。
- 租户内自定义角色、运营、教师、销售、代理、学生。
- 订单、支付、激活码、权益、视频次数。
- 题库导入、内容审核、资源台账。
- 阿里云 OSS、腾讯 COS、Supabase Storage 混合对象存储。
- CRM 推送、销售首绑、分佣结算。
- 未来还要接微信/QQ 登录、微信/支付宝支付、AI 报告。
这些都包含复杂业务规则、密钥、幂等、审计和跨表事务。因此不能让 Taro 前端直接写业务表来替代后端。
## 推荐架构
```text
Taro/H5/小程序
├─ Supabase client
│ ├─ Auth session/JWT
│ ├─ 可选Realtime
│ ├─ 可选:严格 RLS 下的 table/view 只读或低风险自助写入
│ └─ 可选RPC 调用短事务业务函数
└─ apps/api / Edge Functions 业务命令层
├─ 校验 Supabase JWT/session
├─ 解析 tenant
├─ 校验角色和 permissions
├─ 执行业务事务
├─ 写审计日志
├─ 签发对象存储 URL
└─ 调用支付/短信/微信/QQ/CRM/AI provider
```
`apps/api` 可以部署为我们自己的 Node.js API也可以在部分云原生场景下拆为 Supabase Edge Functions。对当前项目而言保留 `apps/api` 更适合复杂业务、国内 provider、自托管和后续 worker。
## 功能落位选择
| 场景 | 首选方案 | 说明 |
| --- | --- | --- |
| 公开只读数据 | view/table + RLS | 例如公开 Banner、FAQ、主题配置 |
| 当前用户简单自助读写 | table/view + RLS | 必须有最小 policy 和越权测试 |
| 原子状态变化 | Postgres RPC | 例如激活码兑换、计数扣减、幂等状态流转 |
| 跨表聚合读取 | view/RPC 或 `apps/api` | 简单聚合可用 view复杂权限用 API |
| 支付/短信/OAuth/CRM/AI | Edge Function 或 `apps/api` | 需要密钥和外部 HTTP 调用 |
| 大批量导入/重试/定时任务 | worker | API 只提交 job 和查询状态 |
| 私有对象存储签名 | Edge Function 或 `apps/api` | 必须先校验 `content_assets` 和权益 |
选择原则:能用 RLS/view/RPC 安全解决的,就不要写一堆重复后端代码;需要密钥、副作用、审计、幂等和第三方 provider 的,就不要放到前端直连表。
## 前端直连 Supabase 的适用范围
| 能力 | 是否建议前端直连 Supabase | 说明 |
| --- | --- | --- |
| Supabase Auth session | 建议 | H5 可直接用 `@supabase/supabase-js`;小程序需先验证运行时兼容性 |
| 获取当前用户 JWT | 建议 | API 请求统一带 `Authorization: Bearer <access_token>` |
| 低风险公开只读数据 | 可选 | 仅限 RLS、grant、索引都成熟后 |
| 原子业务 RPC | 可选 | 仅限函数内部完成 tenant/user/permission 校验 |
| Realtime 通知 | 可选 | 非敏感通知、学习状态刷新可考虑;后台敏感队列不建议前端订阅 |
| Public Storage 公开资源 | 可选 | 真正公开的 Logo、主题图可直连 CDN/公开 bucket |
| 私有资料/PDF/视频 | 不建议 | 必须通过 `content_assets` 权限校验和后端签名 |
| 学习记录/答题/错题/收藏 | 不建议直接写表 | 可通过后端 API 或经过安全评审的 RPC |
| 订单/支付/激活码/优惠券 | 禁止 | 必须走后端事务、验签和幂等 |
| 租户后台配置 | 禁止 | 涉及权限、密钥、审计 |
| 内容导入/题库维护 | 禁止 | 必须走 preview/import/job/issues 管线 |
| 平台超级后台 | 禁止 | 必须走平台管理员鉴权和审计 |
## Taro 运行时建议
Taro 要同时支持 H5 和微信小程序。Supabase 官方 JavaScript client 是通用 JS SDK并提供 browser、React Native、自定义 fetch/storage 等配置方式,但微信小程序环境不等同于标准浏览器。
因此建议:
1. H5 端优先使用 `@supabase/supabase-js` 管理 Auth session。
2. 微信小程序端先做兼容性验证:
- `fetch` 或 request adapter。
- storage adapter。
- URL polyfill。
- token auto refresh。
3. 如果小程序端 `supabase-js` 兼容成本高,则小程序只调用 `apps/api/auth/*`
- 前端把微信 `code` 或手机号验证码发给后端。
- 后端调用 Supabase Auth/Admin 或自有 session 逻辑换取可信 session。
- 前端保存后端返回的 access token/session。
4. 无论 H5 还是小程序,复杂业务数据默认调用 `apps/api`、Edge Function 或 RPC不直接写 Supabase 表。
## 推荐前端环境变量
只允许出现在前端构建中的变量:
```text
TARO_APP_API_BASE_URL=https://api.example.com
TARO_APP_SUPABASE_URL=https://<project-ref>.supabase.co
TARO_APP_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxx
```
禁止出现在前端:
```text
SUPABASE_SECRET_KEY
SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL
ALIYUN_OSS_ACCESS_KEY_SECRET
TENCENT_COS_SECRET_KEY
WECHAT_PAY_PRIVATE_KEY
ALIPAY_APP_PRIVATE_KEY
AUTH_SESSION_SECRET
PLATFORM_ADMIN_API_KEY
```
## API 请求目标形态
当前迁移期:
```text
Authorization: Bearer <tk_session>
x-tenant-id: <tenantId>
x-user-id: <userId>
```
生产目标:
```text
Authorization: Bearer <supabase_access_token_or_server_session>
```
生产时后端负责:
- 验证 JWT。
- 从 JWT/session 获取 userId。
- 根据 host/tenantCode/JWT claims 解析 tenant。
- 校验用户属于该租户。
- 校验角色和权限。
- 执行业务逻辑。
前端不再传 `x-user-id`,也不能靠传 `tenantId` 获得跨租户数据。
## 对后续 AI/开发者的硬性约束
- 不要把 Supabase-first 误解成前端直写所有表。
- 不要让 Taro 直接写订单、支付、权益、租户配置、CRM、导入相关表。
- 不要把 service role/secret key 放进 Taro。
- 不要为了少写接口而放宽 RLS。
- 新增前端直连 Supabase table/view/RPC 之前,必须先补:
- 明确 RLS policy。
- 最小 grant。
- 跨租户测试。
- 权限测试。
- 性能索引。
- 若某个功能需要密钥、webhook、审计、幂等、长任务或第三方 provider一律放到 `apps/api`、Edge Function 或 worker。