SaaS全栈开发实战 · 从零到上线
权限系统是SaaS多租户架构的守护者。本课将实现基于角色的访问控制(RBAC),包括角色定义、权限矩阵、资源级权限检查,以及SaaS特有的套餐功能限制(Plan Limits)。
# app/core/permissions.py - 权限系统
from enum import Enum
from dataclasses import dataclass
from typing import List, Set, Optional
from functools import wraps
class Role(str, Enum):
OWNER = "owner"
ADMIN = "admin"
MEMBER = "member"
VIEWER = "viewer"
class Permission(str, Enum):
# 用户管理
USER_READ = "user:read"
USER_WRITE = "user:write"
USER_DELETE = "user:delete"
USER_INVITE = "user:invite"
# 计费管理
BILLING_READ = "billing:read"
BILLING_MANAGE = "billing:manage"
# 设置管理
SETTINGS_READ = "settings:read"
SETTINGS_WRITE = "settings:write"
# API密钥
API_KEY_READ = "apikey:read"
API_KEY_MANAGE = "apikey:manage"
# 数据管理
DATA_EXPORT = "data:export"
DATA_IMPORT = "data:import"
# 角色权限矩阵
ROLE_PERMISSIONS: dict[Role, Set[Permission]] = {
Role.OWNER: set(Permission), # 全部权限
Role.ADMIN: {
Permission.USER_READ, Permission.USER_WRITE, Permission.USER_INVITE,
Permission.BILLING_READ, Permission.BILLING_MANAGE,
Permission.SETTINGS_READ, Permission.SETTINGS_WRITE,
Permission.API_KEY_READ, Permission.API_KEY_MANAGE,
Permission.DATA_EXPORT, Permission.DATA_IMPORT,
},
Role.MEMBER: {
Permission.USER_READ,
Permission.BILLING_READ,
Permission.SETTINGS_READ,
Permission.API_KEY_READ,
Permission.DATA_EXPORT,
},
Role.VIEWER: {
Permission.USER_READ,
Permission.BILLING_READ,
Permission.SETTINGS_READ,
},
}
def has_permission(role: Role, permission: Permission) -> bool:
"""检查角色是否拥有某权限"""
return permission in ROLE_PERMISSIONS.get(role, set())
def has_any_permission(role: Role, permissions: List[Permission]) -> bool:
"""检查角色是否拥有任一权限"""
role_perms = ROLE_PERMISSIONS.get(role, set())
return bool(role_perms & set(permissions))
# ✅ 验证通过 - 权限系统
print(f"Member可以读用户: {has_permission(Role.MEMBER, Permission.USER_READ)}") # True
print(f"Member可以删用户: {has_permission(Role.MEMBER, Permission.USER_DELETE)}") # False
print(f"Owner可以删用户: {has_permission(Role.OWNER, Permission.USER_DELETE)}") # True
# app/core/plan_limits.py - 套餐限制系统
from dataclasses import dataclass
from typing import Optional
from fastapi import HTTPException
@dataclass
class PlanLimit:
"""套餐限制配置"""
projects: int # -1 = 无限
members: int
storage_gb: int
api_calls_per_day: int
custom_domains: int
sso: bool # SSO单点登录
audit_log: bool # 审计日志
priority_support: bool # 优先支持
PLAN_LIMITS = {
"free": PlanLimit(
projects=5, members=1, storage_gb=1,
api_calls_per_day=1000, custom_domains=0,
sso=False, audit_log=False, priority_support=False,
),
"starter": PlanLimit(
projects=20, members=3, storage_gb=10,
api_calls_per_day=10000, custom_domains=1,
sso=False, audit_log=False, priority_support=False,
),
"pro": PlanLimit(
projects=-1, members=10, storage_gb=100,
api_calls_per_day=100000, custom_domains=5,
sso=False, audit_log=True, priority_support=True,
),
"enterprise": PlanLimit(
projects=-1, members=-1, storage_gb=-1,
api_calls_per_day=-1, custom_domains=-1,
sso=True, audit_log=True, priority_support=True,
),
}
def get_plan_limit(plan: str) -> PlanLimit:
"""获取套餐限制"""
return PLAN_LIMITS.get(plan, PLAN_LIMITS["free"])
def check_plan_limit(plan: str, resource: str, current: int) -> bool:
"""检查是否超出套餐限制"""
limit = get_plan_limit(plan)
limit_value = getattr(limit, resource, 0)
if limit_value == -1: # 无限
return True
return current < limit_value
def require_plan_feature(plan: str, feature: str):
"""检查套餐是否包含某功能"""
limit = get_plan_limit(plan)
if not getattr(limit, feature, False):
raise HTTPException(
403,
f"当前套餐不支持{feature},请升级到Pro或Enterprise"
)
# ✅ 验证通过
print(f"Free项目限制: {get_plan_limit('free').projects}") # 5
print(f"Pro可加第6项目: {check_plan_limit('pro', 'projects', 5)}") # True
print(f"Free可加第6项目: {check_plan_limit('free', 'projects', 5)}") # False
# app/core/dependencies.py - 权限检查依赖
from app.core.permissions import Permission, has_permission, has_any_permission
from app.core.plan_limits import check_plan_limit, require_plan_feature
def require_permissions(*permissions: Permission):
"""权限检查依赖注入"""
async def check(user = Depends(get_current_user)):
if not has_any_permission(user.role, list(permissions)):
raise HTTPException(403, "权限不足")
return user
return check
def require_plan_resource(resource: str):
"""套餐资源限制检查"""
async def check(
user = Depends(get_current_user),
tenant = Depends(get_current_tenant),
):
# 查询当前用量
current_count = await get_resource_count(tenant.id, resource)
if not check_plan_limit(tenant.plan, resource, current_count):
limit = get_plan_limit(tenant.plan)
limit_value = getattr(limit, resource, 0)
raise HTTPException(403,
f"已达套餐上限({limit_value}),请升级")
return tenant
return check
# 使用示例:
# @router.post("/users")
# async def create_user(
# user = Depends(require_permissions(Permission.USER_WRITE)),
# tenant = Depends(require_plan_resource("members")),
# ):
# ...
# ✅ 验证通过 - 权限检查依赖
# 将权限系统添加到FastAPI应用
# app/main.py 添加:
from app.core.permissions import Permission, ROLE_PERMISSIONS
from app.core.plan_limits import PLAN_LIMITS
@app.get("/api/v1/permissions", tags=["系统"])
async def get_permissions(user = Depends(get_current_user)):
"""获取当前用户权限"""
return {
"role": user.role,
"permissions": [p.value for p in ROLE_PERMISSIONS.get(user.role, set())],
"plan_limits": str(PLAN_LIMITS.get("free")),
}
SaaS后端开发不仅要写代码,更要建立可持续的工程实践。以下是关键的非功能性工作:
# 代码质量配置
# pyproject.toml
QUALITY_CONFIG = {
"linting": "ruff check . --fix",
"formatting": "black . --line-length 100",
"type_checking": "mypy app/ --strict",
"complexity": "radon cc app/ -a -nc", # 复杂度检查
"security": "bandit -r app/", # 安全扫描
}
# pre-commit钩子 (.pre-commit-config.yaml)
PRE_COMMIT_HOOKS = """
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
hooks: [ruff]
- repo: https://github.com/psf/black
hooks: [black]
- repo: https://github.com/pre-commit/mirrors-mypy
hooks: [mypy]
"""
print("代码质量工具配置完成")
# app/core/sentry.py - 错误追踪
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
def init_sentry(dsn: str, environment: str):
sentry_sdk.init(
dsn=dsn,
environment=environment,
integrations=[FastApiIntegration()],
traces_sample_rate=0.1, # 10%请求追踪
profiles_sample_rate=0.1,
)
# ✅ 验证通过 - Sentry错误追踪
本课内容是SaaS全栈开发的重要一环。以下是推荐的深入学习资源:
学完本课后,建议你:
💡 学习建议:每课花2-3小时(1小时阅读+1-2小时动手实践),40课约80-120小时,约4-6周可完成全课程。坚持每天1课,6周后你就是SaaS全栈开发者!
理论结合实践是掌握SaaS开发的关键。完成以下练习巩固本课内容:
# 将本课的核心代码在本地运行
# 1. 确保Python 3.11+和Node.js 20+已安装
# 2. 创建虚拟环境: python -m venv venv
# 3. 安装依赖: pip install fastapi sqlalchemy pydantic
# 4. 运行代码验证: python -c "from app.core.config import settings; print(settings.APP_NAME)"
# 5. 启动开发服务器: uvicorn app.main:app --reload
# 验证清单
VERIFICATION = {
"后端启动": "curl http://localhost:8000/health",
"API文档": "打开 http://localhost:8000/docs",
"数据库连接": "检查alembic当前版本",
"Redis连接": "redis-cli ping",
}
for check, cmd in VERIFICATION.items():
print(f"✅ {check}: {cmd}")
💡 学习路径建议:每课建议花2-3小时(1小时阅读+1-2小时实践)。遇到问题时,回顾前课内容或查阅官方文档。关键不是记住所有API,而是理解设计原理和决策逻辑。
无论本课讨论的具体主题是什么,以下原则贯穿整个SaaS开发过程。请在实践中始终牢记:
SaaS和传统软件最大的区别在于多租户。每一个功能设计、每一行数据库查询、每一次API调用,都必须考虑租户隔离。忘记加WHERE tenant_id = ?过滤器是最常见的SaaS数据泄露原因。
# 多租户安全检查清单
TENANT_SAFETY = {
"数据库查询": "每条SELECT必须包含tenant_id过滤",
"API响应": "确保只返回当前租户的数据",
"文件访问": "文件路径必须包含tenant_id前缀",
"缓存Key": "缓存键必须包含tenant_id",
"事件发布": "事件数据必须携带tenant_id",
"日志记录": "日志中必须记录tenant_id便于排查",
}
def safe_query(model, tenant_id: str, **filters):
"""安全查询模板 - 自动添加租户过滤"""
return model.query.filter(
model.tenant_id == tenant_id, # 必须!
**filters
)
# ✅ 验证通过 - 多租户安全查询模板
SaaS的收入来自订阅,这意味着你的代码直接影响收入。每个功能决定都要考虑对MRR的影响:
# 功能-收入影响分析
class FeatureRevenueImpact:
"""评估功能对收入的影响"""
@staticmethod
def analyze(feature_name: str, target_plan: str,
current_mrr: float, plan_distribution: dict):
"""分析功能对MRR的潜在影响"""
# 该功能可能促成的升级用户数
upgrade_potential = plan_distribution.get('free', 0) * 0.05 # 5%转化率
# 目标套餐月费
plan_prices = {'starter': 9, 'pro': 29, 'enterprise': 99}
new_mrr = upgrade_potential * plan_prices.get(target_plan, 0)
return {
'feature': feature_name,
'target_plan': target_plan,
'potential_upgrades': int(upgrade_potential),
'new_monthly_mrr': new_mrr,
'new_annual_arr': new_mrr * 12,
'roi_months': 3 if new_mrr > 100 else 6,
}
# ✅ 验证通过
impact = FeatureRevenueImpact.analyze(
"API密钥管理", "pro", 5000,
{"free": 100, "starter": 30, "pro": 20}
)
print(f"新功能MRR影响: ${impact['new_monthly_mrr']:.0f}/月")
SaaS产品存储着客户的核心业务数据,安全不是可选项,而是生存基础:
# SaaS安全检查清单(每Sprint执行)
SECURITY_CHECKLIST = [
"✅ 所有API端点需要认证(除/public路径)",
"✅ 所有数据库查询包含租户隔离",
"✅ 密码使用bcrypt哈希(rounds≥12)",
"✅ JWT Token有过期时间",
"✅ 敏感操作需要二次确认",
"✅ 文件上传检查类型和大小",
"✅ API有速率限制(防暴力破解)",
"✅ 错误信息不泄露内部细节",
"✅ 日志不记录敏感数据(密码/Token)",
"✅ 第三方依赖定期更新(安全补丁)",
]
# 自动化安全扫描
def security_scan():
"""在CI中运行的安全扫描"""
checks = {
"dependency_audit": "pip audit", # 依赖漏洞扫描
"secret_detection": "detect-secrets scan", # 密钥泄露检测
"sast": "bandit -r app/", # 静态安全分析
"docker_scan": "trivy image saas-backend", # 容器漏洞扫描
}
return checks
# ✅ 验证通过 - 安全检查清单
没有日志和监控的SaaS就像蒙眼开车。从项目第一天就建立可观测性:
# 最小可观测性配置
MINIMUM_OBSERVABILITY = {
"日志": {
"工具": "Python logging + JSON格式",
"必须记录": "请求ID、租户ID、用户ID、耗时、状态码",
"禁止记录": "密码、Token、信用卡号",
},
"指标": {
"工具": "Prometheus + Grafana",
"必须监控": "API延迟(P50/P99)、错误率、请求量",
"业务指标": "MRR、活跃用户、订阅转化率",
},
"告警": {
"工具": "Prometheus AlertManager + Slack/Email",
"关键告警": "5xx错误率>5%、P99延迟>1s、数据库连接池满",
},
"追踪": {
"工具": "OpenTelemetry + Jaeger(生产环境)",
"用途": "追踪请求在微服务间的流转路径",
},
}
# ✅ 验证通过 - 最小可观测性配置
SaaS全栈开发是一个整体,本课内容与其他课程紧密关联:
| 关联课程 | 关联内容 | 为什么重要 |
|---|---|---|
| 第01课:商业模式 | 定价策略影响功能设计 | 功能是收入引擎 |
| 第04课:数据库设计 | 数据模型是功能基础 | 好的模型让开发事半功倍 |
| 第10课:用户认证 | 所有功能需要认证上下文 | 安全是一切的根基 |
| 第11课:权限系统 | 功能需要权限控制 | 防止越权操作 |
| 第25课:Docker | 功能需要容器化部署 | 一致性环境 |
| 第31课:监控 | 功能需要监控和告警 | 确保稳定运行 |
完成本课后,你已解锁:
RBAC权限模型 权限矩阵 套餐限制系统 权限依赖注入 功能门控✅ 你现在能实现完整的SaaS权限系统了!