Basic_Web 邮件系统、RBAC权限、定时任务

· Basic_Web

今日进展

  1. 邮件系统:多通道容灾(Resend + SMTP 双通道)、数据库模板管理、发送记录追踪
  2. RBAC 多端权限:角色-权限-平台三维模型,管理员后台角色/权限管理
  3. 定时任务引擎:数据库配置驱动 + 热重载 + Redis JobStore 多实例安全
  4. 分布式限流器:登录5次/5分钟,注册10次/小时,防暴力破解
  5. 审计日志:管理员操作自动记录,操作人/类型/目标/IP 全链路追踪
  6. 管理后台 API:用户管理、角色管理、权限管理、邮件管理、系统审计
  7. 公开 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 继承并扩展