FAB 后端开发框架

想了解平台后端整体框架(怎么加视图/接口、怎么做权限、怎么写定时/异步任务、怎么用缓存、怎么升级数据库、怎么部署)时读这篇——本段的纲领文档

二次开发 / 后端框架
FABFlask-AppBuilder后端框架SSOOA登录RBAC权限角色AdminGammaadd_viewadd_apiMyappModelRestApicelery定时任务异步任务

FAB 后端开发框架(Flask-AppBuilder)

本文是 CubeStudio 后端二次开发的纲领文档。它源自通用 FAB 框架介绍,部分示例为通用 FAB 概念演示;凡与本仓库实际代码有出入处,下文已就近用 文件:行号 标注或改正。

简介

几分钟快速搭建前后端管理控制台,集成 OA 登录、RBAC 权限控制、定时调度、缓存、公司平台 SDK、前后端接口自动封装、用户行为记录、数据库升级管理、docker 镜像、docker-compose 调试、k8s 部署。

框架由来

现在对每位开发者全栈能力的要求越来越强烈。团队里经常会产出一些工具优化工作效率,工具共享逐步形成管理端控制台,就开始需要有前后端能力介入。此时就要求开发者具有管理控制台的开发能力,而这些控制台大部分又是 CURD 的基本操作。本项目采用 FAB(Flask-AppBuilder)框架,部署及生成前后端代码,能够快速部署自己的前后端应用。

目前基于 FAB 的开源项目有很多:airflow、superset 都是典型案例。本项目在开源 FAB 基础上改造为前后端分离模式。

环境准备

  • 在本地安装 docker、docker-compose 环境。
  • 你需要有一个 MySQL 数据库(开源/通用场景下创建名为 myapp 的 db;本商业版 docker-compose 中默认库名为 kubeflow)。
docker run -p 3306:3306 --restart always --name mysql \
  -e MYSQL_ROOT_PASSWORD=admin \
  -v $PWD/docker-add-file/mysqld.cnf:/etc/mysql/conf.d/mysqld.cnf \
  -d mysql:8.0.32

# 进入数据库创建一个 db(通用示例)
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

核实并修正:本商业版仓库的数据库连接由环境变量决定,install/docker/config.py:476 读取 MYSQL_SERVICE(其次 POSTGRESQL_SERVICE/KINGBASE_SERVICE/DM_SERVICE)。install/docker/docker-compose.yml 中 MySQL 服务的 MYSQL_DATABASE 默认是 kubeflow,而非通用示例里的 myapp。同时支持 PostgreSQL / 人大金仓 / 达梦(按 URI 自动切换 migrations/versions-*)。

启动

cd install/docker/
# docker-compose.yml 文件中可以修改 redis / mysql 的配置
docker-compose up   # 即可启动
# 然后 http://127.0.0.1:80/ 就可以访问

说明:本商业版 install/docker/docker-compose.yml 默认启用的服务为 redismysqlfrontend(nginx 前端容器)、myapp(后端容器);beatworkerwatch 三个服务在文件中以注释形式提供,按需放开。后端容器默认 commandsleep 占位,需 exec 进容器执行 /entrypoint.shpython myapp/run.py(见 install/docker/docker-compose.yml)。

用户和权限

SSO 登录

多用户认证授权的相关逻辑在 security.py 中,开源版本默认为账号密码登录。若想添加 OA 认证,可以在 MyCustomRemoteUserView 中添加对应的函数(已添加 API 访问版的 OA 认证)。用户打开页面时会自动发起 OA 认证,并自动注册用户,创建和用户同名的角色。如果你需要修改 OA 认证,可以修改 login 函数(myapp/project.py:64)。用户登录后在右上角个人页面都会有一个秘钥,这个秘钥用来在 API 访问时进行认证。

平台向其他三方平台提供 API 接入能力时,只需要其他平台在 HTTP 请求 header 中提供这个字段,就能正常识别是哪个用户,后面的授权工作就和 Web 访问相同了。

权限管理

登录后还需要进行授权,FAB 使用 RBAC 进行权限管理:用户可以绑定不同的角色,角色可以绑定不同的权限,这样用户就具有了指定的权限。

用户层

系统自带了一个 admin 用户,每个用户登录时会自动注册,同时 admin 也可以手动创建用户。框架还添加了后门链接,管理员可借此模拟其他用户登录、查看用户登录后状态。

角色层

系统自带 AdminGamma 角色(myapp/security.py:250 描述:Admin 拥有管理员权限,Gamma 为普通用户角色)。每个用户登录时会自动创建与用户名 username 同名的角色,并将用户绑定到该同名角色和 Gamma 角色上,所以普通用户进来会提示各种没有权限。具有 Admin 角色的用户拥有一切权限(为其他用户加角色、为角色加权限等)。系统自带只有 admin 用户具有 Admin 角色,默认账号密码为 admin/admin

核实:新用户默认注册角色由 myapp/config.py:94AUTH_USER_REGISTRATION_ROLE = "Gamma" 决定;Admin/Gamma 的绑定见 myapp/security.py:871-872

你可以手动添加各种形式的角色:项目组、组织架构、一类用户等。

权限层

权限层包含两部分:视图菜单 view/menu 和权限类型 Base Permissions。即在哪些对象上具有哪些权限(比如在 table1 上具有 add 权限,在 menu1 上具有 click 权限)。

在代码中一般 view/menu 对应 class,Base Permissions 对应 function。所以视图菜单和权限类型两者之间并不是可以随意绑定的。系统自带的视图权限可以在 Web 界面上查看,主要是某些菜单的点击权限、某些 model(table) 的增删改查权限、某些函数的执行权限。

如果想为自己的函数或 model 注册视图权限,只需要在函数上添加 has_access 修饰符:

from flask_appbuilder.security.decorators import has_access

@has_access
def list(self):
    pass

这样用户就需要具有该函数的访问权限才能正常进入,否则会报权限异常。

建议基于 myapp/views/baseApi.py 中的 MyappModelRestApi 来编写接口,所有接口都默认做了权限注册。

核实并修正:原文写"建议基于 /view/base.py 中 MyappModelView 编写 api 接口",但当前仓库不存在 MyappModelViewbase.py 提供的是 BaseMyappViewmyapp/views/base.py:272),前后端分离的接口基类是 MyappModelRestApimyapp/views/baseApi.py:243),各业务视图均继承自它(例如 myapp/views/view_metadata_metric.py:191)。

添加页面视图和接口

系统自带了 model(数据库表记录)的增删改查接口和页面,所以用户需要做的就只有定义和注册 model 表结构。

本仓库为前后端分离模式:实际开发以"定义 model + 继承 MyappModelRestApi + appbuilder.add_api(...)"为主(见下方第 3 节),菜单入口在 home.py/menu 中下发。下方第 1、2 节为通用 FAB 概念演示。

1、仅添加菜单链接

home.pymenu 接口中添加(详见 新增视图页面与数据库升级.md)。

2、同时添加 CURD 前端界面和后端接口(通用 FAB 概念)

需要先定义 model(表结构),再定义接口视图类,自动生成前后端代码,最后添加链接到菜单:

# 定义 model
class Model2(Model):
    __tablename__ = 'model2'
    id = Column(Integer, primary_key=True)
    attr1 = Column(String(50), unique=True, nullable=False)

    def __repr__(self):
        return self.attr1

# 定义数据表视图
class Model2_ModelView(MyappModelView):
    datamodel = SQLAInterface(Model2)

appbuilder.add_view(
    baseview=Model2_ModelView,
    name="submenu2",
    icon='fa-address-book-o',
    category='menu1',
    category_icon='fa-envelope'
)

核实并修正:上例为服务端渲染的传统 FAB 用法(MyappModelView + appbuilder.add_view)。当前 CubeStudio 商业版未使用 appbuilder.add_view(...),也没有 MyappModelView 类,应改用第 3 节的前后端分离写法。此处保留以说明 FAB 原始能力。

因为涉及前端显示,可配置参数较多,下面列举一些(均可不配置,使用默认)。这些参数在前后端分离的 MyappModelRestApi 中同样适用:

base_permissions       # model 管理的操作类型
add_fieldsets          # 创建页面分组展示
edit_fieldsets         # 编辑页面分组展示
label_columns          # 列别名
list_columns           # list 页面显示的列
show_fieldsets         # 单条记录详情分组
base_filters           # list 页面默认筛选条件
base_order             # list 页面排序方法
validators_columns     # add/update 表单自动校验
# 关联字段自定义查询过滤器:add_query_rel_fields / edit_query_rel_fields / search_query_rel_fields
# add_query_rel_fields = {'attr3': [['attr1', FilterStartsWith, 'a']]}
# 自定义页面模板:show_template / edit_template / add_template

def pre_add(self, obj): ...      # 添加前
def post_add(self, obj): ...     # 添加后
def pre_update(self, obj): ...   # 更新前
def post_update(self, obj): ...  # 更新后
def pre_delete(self, obj): ...   # 删除前
def post_delete(self, obj): ...  # 删除后

# 批量操作函数
@action("muldelete", "Delete", "Delete all Really?", "fa-rocket", single=False)
def muldelete(self, items): ...

3、添加 CURD 纯后端接口(本仓库主流写法)

先定义 model,再定义视图类,注册 API 接口:

# 定义数据表视图
class Model3_ModelView_Api(MyappModelRestApi):
    datamodel = SQLAInterface(Model2)
    route_base = '/model2/api'

appbuilder.add_api(Model3_ModelView_Api)

核实:这正是仓库实际写法,例如 Metadata_metric_ModelView_Api(... , MyappModelRestApi)route_base = '/metadata_metric_modelview/api',并以 appbuilder.add_api(...) 注册(myapp/views/view_metadata_metric.py:191)。注意:route_base 要与 home.py 菜单中 API 类型 url 对应。

4、添加自定义后端接口

自己定义一批接口,然后将类注册到系统中:

class Myapp(BaseMyappView):
    route_base = '/myapp'
    default_view = 'welcome'

    @expose('/welcome')
    def welcome(self):
        if not g.user or not g.user.get_id():
            return redirect(appbuilder.get_url_for_login)
        msg = 'Hello ' + g.user.username + " !"
        return self.render_template('hello.html', msg=msg)

# add_view_no_menu 添加视图,但不在 FAB 自带菜单显示
appbuilder.add_view_no_menu(Myapp)

核实:add_view_no_menu 在仓库中确有使用,如 view_chat_dingtalk.py:192view_chat_wework.py:164view_chat_wechat.py:486view_labelstudio.py:696 等;菜单入口由 /myapp/menu 下发。(注:view_blood.py 用的是 appbuilder.add_api(Blood),不属于此类。)

5、Flask 的传统方法

因为 FAB 基于 Flask,所以你也可以用 Flask 的任何方法,例如注册路由:

@app.route("/health")
def health():
    return "OK"

外部组件调用

参考 01-架构原理/基础组件介绍.md

定时调度能力

管理控制台难免有很多离线定时任务或异步任务。框架集成了 celery,可以实现定时任务、异步任务的发起和执行。在 config.pyCeleryConfig 类中配置定时框架参数和要执行的任务参数。系统默认使用 redis 作为任务队列和结果存储数据库。

任务编写

任务编写在 tasks 目录下,框架配置了示例任务 tasks/schedules.py,并添加了任务结果的微信推送:

# 配置 celery 任务
@celery_app.task(name="task.task_name1", bind=True)
def task_name1(task):
    pass

核实:myapp/tasks/schedules.py 中实际任务即此形态,如 @celery_app.task(name="task.delete_workflow", bind=True) / def delete_workflow(task)schedules.py:133-134)。

配置任务调度参数

config.pyCeleryConfig 类中,task_annotations 用来配置单个任务执行时的限制条件:

# 任务的限制,key 是 celery_task 的 name,值是限制配置
task_annotations = {
    'task.task_name1': {
        'rate_limit': '1/s',      # 任务的调度速率限制
        'time_limit': 1200,       # 运行时长限制,超时直接退出
        'soft_time_limit': 1200,  # 运行时长限制,会抛异常,可 catch
        'ignore_result': True,
    },
}

beat_schedule 用来配置定时任务的调度周期:

# 定时任务的配置项,key 为 celery_task 的 name,值是调度配置
beat_schedule = {
    'task_task1': {
        'task': 'task.task_name1',  # 控制任务
        'schedule': 10.0,           # 调度周期(秒)
        # 'schedule': crontab(minute='1', hour='*'),  # 也可用 crontab
    }
}

核实并修正(重要):原文使用旧式大写常量名 CELERY_ANNOTATIONS / CELERYBEAT_SCHEDULE。本仓库 myapp/config.pyCeleryConfig 采用 Celery 5 小写配置名:task_annotationsconfig.py:602)、beat_scheduleconfig.py:691),以及 broker_urlresult_backendimports = ('myapp.tasks',)。请按小写名编写。

任务的调度

框架定时任务基于 celery,可直接用 celery 命令启动 beat 进程产生定时任务:

celery --app=myapp.tasks.celery_app:celery_app beat --loglevel=info

在 docker-compose 中包含(注释形态)beat 服务,用来产生定时任务并推送到 redis。

任务的执行

定时任务发送到 redis 后,需要 worker 接收执行。同样用 celery 命令启动多个 worker 并发执行:

celery --app=myapp.tasks.celery_app:celery_app worker --loglevel=info --pool=prefork -Ofair -c 2 -n worker@%h

在 docker-compose 中包含(注释形态)worker 服务,创建 worker 容器接收并执行任务。

缓存

框架包含缓存配置,config.pyCACHE_CONFIG 为缓存配置(config.py:519),系统默认使用 redis 作为缓存数据库(CACHE_REDIS_URL 使用 redis 的 1 号库)。代码中使用缓存:

from myapp import cache             # 引入(myapp/__init__.py:230 装配)
cache.set('msg', msg, timeout=60)   # 设置
cache_value = cache.get('msg')      # 读取

用户行为记录

框架已集成用户行为记录。如果想收集用户对指定函数的访问记录,只需在函数上添加修饰符,会将用户或 API 访问的行为记录到数据表 logs 中:

from myapp import event_logger       # myapp/__init__.py:390 装配
@event_logger.log_this
def list(self):
    pass

这样就可以在用户行为记录中看到哪个用户、什么时间、访问了哪个路径、做了什么操作、传递了什么参数。

数据库结构升级和回滚

基于 FAB 做 model 的新增或修改,对应数据表的变更。框架基于 Flask-Migrate 集成了数据库结构的升级和回滚。前提是:myapp/__init__.py 引入时能够引入该 model(__init__.py 最下面引入 views,每个 view 又引入对应 model,这样被引入的 model 才能被系统识别,用来判断升级或回滚策略)。

数据库管理文件在 myapp/migrations 下,升级记录在 myapp/migrations/versions 目录下。如果你新增或修改了 model(且可被系统发现),在容器的 /home/myapp/ 目录下执行:

myapp db migrate   # 生成对应版本数据库表的升级文件到 versions 文件夹下

系统会对比当前连接数据库的表结构与代码中定义的表结构差异,生成升级文件到 versions 目录。所以数据库一定不能手动修改,否则升级记录文件就不再连贯。

有了升级记录文件,直接执行下面命令进行升级:

myapp db upgrade   # 数据库表同步更新到 mysql

完整的"upgrade → migrate → upgrade"三步法见 新增视图页面与数据库升级.md。本仓库 myapp/migrations 下另有 versions-mysqlversions-postgres 两份历史结构。启动时的切换逻辑见 install/docker/config.py:476-488:配 MYSQL_SERVICE 时直接用默认 versions(不拷贝);配 POSTGRESQL_SERVICE/KINGBASE_SERVICE 时用 versions-postgres 覆盖 versions;配 DM_SERVICE(达梦)时清空 versions,改由 db.create_all() 建表、不走 Alembic 迁移。

docker 镜像封装

项目包含 docker 镜像封装,为方便代码调试快速迭代,镜像分两部分:

  1. install/docker/Dockerfile-base:基础环境封装,耗时较久但环境部分改动小,封装一次可长期复用(FROM ubuntu:22.04,Python 3.9)。
  2. install/docker/Dockerfile:在基础镜像之上加封代码,因为只是 copy 代码,封装较快,适合短周期迭代(FROM ...kubeflow-dashboard:base-python3.9-...)。

本地调试

由于本框架涉及多个容器组件,本地调试使用 docker-compose 启动;也可用 docker-compose 做生产部署,但只能在单机上进行,无法避免单点故障。

单机部署调试的配置在 install/docker/docker-compose.yml,其中包含 redis/myapp/frontend/mysql 服务(以及注释形态的 beat/worker/watch)。需求简单时可注释掉不需要的部分。

为方便调试,一般会将代码目录、配置文件,甚至本地 python 库挂载到容器中:

- ../../myapp/:/home/myapp/myapp/
- ./entrypoint.sh:/entrypoint.sh
- ./config.py:/home/myapp/myapp/config.py

这样你就可以直接在 IDE 中编辑,容器中也能检测到自动加载执行。注意:dev 模式能自动检测文件变更热加载,prod 模式不能自检测,需要手动重启。

k8s 高可用部署

本地调试完成后上生产线。应用简单或仅内部使用时可用上面的 docker-compose 部署;更推荐部署上云,install/kubernetes/ 目录中包含项目所需应用的全部 k8s 部署文件(需要一定的 k8s 基础)。云上部署详见 02-部署安装 段。通用 FAB 版本参考 https://github.com/data-infra/fab/tree/main/install/kubernetes

最后更新 2026-07-09完整文档以官方仓库为准:GitHub Wiki