Basic_Web 邮件系统、RBAC权限、定时任务
· Basic_Web
今日进展
- 邮件系统:多通道容灾(Resend + SMTP 双通道)、数据库模板管理、发送记录追踪
- RBAC 多端权限:角色-权限-平台三维模型,管理员后台角色/权限管理
- 定时任务引擎:数据库配置驱动 + 热重载 + Redis JobStore 多实例安全
- 分布式限流器:登录5次/5分钟,注册10次/小时,防暴力破解
- 审计日志:管理员操作自动记录,操作人/类型/目标/IP 全链路追踪
- 管理后台 API:用户管理、角色管理、权限管理、邮件管理、系统审计
- 公开 API 前缀:/api/v1/public/* 无需认证,兼容旧接口路由
关键代码/伪代码
邮件系统 — 多通道容灾
# 邮件系统最怕什么?主通道挂了。
# 之前用 Resend 发邮件,有天 Resend 服务挂了2小时,
# 验证码发不出去,用户注册全卡住了。
# 解决方案:多通道容灾,主通道挂了自动切备用
CLASS EmailRouter:
"""邮件通道路由 — 主通道挂了自动切备用"""
ASYNC DEF send(self, to: str, template_code: str, params: dict):
# 从数据库加载模板
template = AWAIT self.template_repo.get_by_code(template_code)
# 渲染模板(支持变量替换)
subject = template.render_subject(params)
body = template.render_body(params)
# 尝试主通道
TRY:
result = AWAIT self.primary_provider.send(to, subject, body)
AWAIT self.log_success(to, template_code, "primary")
RETURN result
EXCEPT ProviderError:
logger.warning("email_primary_failed", provider="resend")
# 主通道失败,切备用通道
result = AWAIT self.fallback_provider.send(to, subject, body)
AWAIT self.log_success(to, template_code, "fallback")
RETURN result
RBAC 多端权限 — 角色-权限-平台三维模型
# 普通 RBAC 的问题:角色只有"能做什么"
# 但实际场景:同一个管理员在 Web 端和 App 端能做的事不一样
# 解决方案:加一个维度——平台(Platform)
# 数据模型
# User → Role → PlatformPermission → Permission
# ↑ 这里多了个 Platform 维度
CLASS PermissionService:
"""权限服务 — 按平台过滤权限"""
ASYNC DEF get_permissions(self, user_id: str, platform: str) -> list[str]:
# 1. 获取用户所有角色
roles = AWAIT self.role_repo.get_user_roles(user_id)
# 2. 按平台过滤权限
permissions = []
FOR role IN roles:
perms = AWAIT self.perm_repo.get_platform_permissions(
role_id=role.id,
platform=platform # "web" / "app" / "mini"
)
permissions.extend(perms)
RETURN deduplicate(permissions)
# 管理员后台可以配置:
# "内容编辑"角色 → Web端:可编辑/可发布
# "内容编辑"角色 → App端:只可查看
# 同一个人,不同端,不同权限
定时任务 — 数据库配置驱动 + 热重载
# 传统定时任务的问题:改个 cron 表达式要重启服务
# 多实例部署的问题:3个实例同一个任务跑3次
# 解决方案:数据库配置 + 热重载 + Redis JobStore
CLASS SchedulerRegistry:
"""定时任务注册中心 — 数据库配置驱动"""
ASYNC DEF start(self):
# 从数据库加载所有任务配置
tasks = AWAIT self.task_repo.get_enabled()
FOR task IN tasks:
# 用 APScheduler + Redis JobStore 注册
self.scheduler.add_job(
task.handler,
trigger=CronTrigger.from_crontab(task.cron_expr),
id=task.code,
jobstore="redis", # 多实例安全
)
# 启动热重载检查线程
self._start_reload_watcher()
DEF _start_reload_watcher(self):
# 每分钟检查 updated_at 是否变化
# 变了就重载对应任务,不用重启服务
WHILE True:
sleep(60)
latest = AWAIT self.task_repo.get_updated_since(self.last_check)
FOR task IN latest:
self.scheduler.reschedule_job(task.code, ...)
self.last_check = now()
遇到的问题
- 邮件模板存在数据库还是文件里?最终选数据库:管理员在后台就能改模板,不用改代码重新部署。代价是迁移时需要种子数据
- RBAC 多端权限的缓存策略:权限变更不频繁,但每次请求都要查。用 Redis 缓存用户权限,角色变更时主动失效
- 定时任务热重载的竞态条件:检查 updated_at 和重载之间可能又更新了。解决方案:用版本号而非时间戳,CAS 更新
明日计划
- Basic_Mini 微信小程序脚手架搭建,与 Basic_Web 前后端对齐
- FinClaw_Web 从 Basic_Web 继承并扩展