Skip to content

Latest commit

 

History

History
316 lines (215 loc) · 20 KB

File metadata and controls

316 lines (215 loc) · 20 KB

magic-dash-pro二次开发指南

magic-dash-pro适合构建多页面、持续扩展的管理型Dash应用,支持复杂用户与部门关系、用户名密码登录、邮件验证码登录、OTP动态口令登录、用户管理、部门管理、权限管理、登录日志、鉴权和数据库持久化能力。开发时需要同时关注页面、权限和数据模型。

新增受保护核心页面

  1. views/core_pages/下新增页面文件,例如:
views/core_pages/report.py
  1. 编写页面渲染函数:
def render():
    return ...
  1. configs/router_config.pycore_side_menu中加入菜单项。

  2. valid_pathnames中登记pathname

"/core/report": "报表页"
  1. 为新页面配置访问规则。

如果新页面属于随代码发布的内置权限基线,可在configs/auth_config.py中按角色调整pathname_access_rules。如果希望运行后由管理员维护,可在“权限管理”界面中调整数据库权限组规则。某个角色使用include模式时,需要把新页面菜单key加入该角色的keys;使用exclude模式时,需要确认该页面是否应被排除。

如果希望管理员后续可在“权限管理”界面中为数据库权限组勾选该页面,请确保该页面以字符串pathname登记在RouterConfig.valid_pathnames中,且没有被加入RouterConfig.public_pathnames。权限管理界面只会列出这类受保护的字符串页面;正则通配页面仍建议通过AuthConfig.pathname_access_rules硬编码维护。

  1. components/page_content.py中导入新页面模块,并在render()函数中补充分发逻辑:
from views.core_pages import report

...

elif pathname == "/core/report":
    page_content = report.render()

如需回调,在callbacks/core_pages_c/下新增对应回调文件。可以参考page1.pylogin_logs.py的写法,在页面模块中导入对应回调模块,或在包初始化逻辑中集中导入,确保回调注册生效。

新增公开页面

公开页面不需要登录即可访问,适合登录页、错误页、开放分享页等。

处理方式:

  • valid_pathnames中登记页面。
  • public_pathnames中加入页面pathname
  • 如果页面不需要核心布局,将pathname加入independent_core_pathnames
  • app.py的根路由root_router()中补充该公开路径对应的渲染分支;否则即使路径被标记为公开,也不会自动知道应渲染哪个页面。
  • 如果该公开页面需要独立渲染,还要在views/core_pages/__init__.py或对应的公开页面渲染入口中补充实际页面返回逻辑。

新增通配页面

通配页面适合详情页这类路径中包含动态参数的页面,例如/core/report/detail/123

处理方式:

  1. RouterConfig.wildcard_patterns中定义正则。
  2. 将正则对象加入RouterConfig.valid_pathnames
  3. 如页面需要独立渲染,将正则对象加入RouterConfig.independent_core_pathnames,并在views/core_pages/__init__.py中补充命中该正则后的页面渲染分支。
  4. 在页面渲染函数中从URL中解析动态参数。
  5. 如果页面需要受角色权限控制,在AuthConfig.pathname_access_rules中按角色维护对应正则对象或页面入口权限。

当前模板内置流程主要演示“独立通配页面”。如果希望新增嵌入核心布局的普通通配页面,还需要同步调整callbacks/core_pages_c/__init__.py中的路径合法性判断和多标签页标题生成逻辑,并在components/page_content.py中补充对应渲染分发。

新增角色与权限组

模板支持两种角色来源:

  • 硬编码角色:写在configs/auth_config.py中,适合系统内置、需要随代码版本管理的基础角色。
  • 数据库权限组:写入models/user_permission_groups.py对应的数据表,适合运行后由管理员在“权限管理”中维护的业务角色。

实际运行时不要直接把AuthConfig.roles视为完整角色清单,应优先通过UserPermissionGroups.get_effective_roles()get_effective_role_options()get_effective_pathname_access_rule()读取综合后的有效角色与权限规则。

如需新增硬编码角色,在configs/auth_config.py中扩展roles

roles = {
    "admin": {"description": "系统管理员"},
    "normal": {"description": "常规用户"},
    "auditor": {"description": "审计人员"},
}

然后在pathname_access_rules中为新角色补充访问规则:

"auditor": {
    "type": "include",
    "keys": ["/core/login-logs"],
}

新增硬编码角色后,通常还需要运行python -m magic_init确保权限分组表存在,再在“系统管理-用户管理”中为用户分配该角色。

如需由管理员在线维护角色,可登录管理员账号,从右上角用户菜单进入“权限管理”,使用“新增权限组”写入数据库权限组。数据库权限组的id会直接作为Users.user_role保存,名称用于页面展示,访问规则用于侧边菜单、页面搜索和直接访问校验。

数据库权限组支持以下访问规则:

规则 行为
all 可访问全部受保护页面
include 仅可访问勾选的页面;首页仍自动放行
exclude 可访问除勾选页面外的其他受保护页面

需要注意:

  • admin权限组只能通过AuthConfig硬编码定义,不能新增、编辑、删除或覆盖;管理员角色必须同时在AuthConfig.rolesAuthConfig.pathname_access_rules中保留明确规则,不能依赖数据库权限组补充管理员权限。
  • 编辑非管理员硬编码角色时,会新增或更新一条同id数据库记录,用数据库内容覆盖该角色的名称和访问规则;删除这条数据库记录即可恢复硬编码配置。
  • 新增数据库权限组时,id和名称不能与任何有效角色重复,也不能与硬编码角色名称冲突。
  • 删除数据库权限组前,如果该权限组已被用户引用,需要先在用户管理中把相关用户调整到其他有效角色。
  • 如果某个用户保存了已失效的角色,访问受保护页面会进入403,用户管理列表会以异常状态展示该角色。

权限生效链路

页面访问权限的实际生效链路如下:

  1. magic_init.py创建或升级UserPermissionGroups数据表。
  2. UserPermissionGroups.get_effective_roles()合成硬编码角色和数据库权限组,供用户管理下拉框、用户列表和页首角色展示使用。
  3. UserPermissionGroups.get_effective_pathname_access_rule()为当前用户角色计算最终页面访问规则。
  4. app.py的根路由先检查登录状态,再按最终规则拦截受保护页面的直接访问。
  5. views/core_pages/__init__.pycomponents/core_side_menu.py基于同一规则过滤页面搜索选项和侧边菜单。

Flask后端仍保留flask-principal相关兼容能力,并通过懒加载的user_permissions支持数据库角色;FastAPI后端不依赖flask-principal,但页面权限校验、管理界面和数据模型与Flask版本保持一致。

相关实现文件

文件 作用
configs/auth_config.py 定义硬编码角色和默认页面访问规则
models/user_permission_groups.py 合成有效角色、校验权限规则、维护数据库权限组
components/permission_manage.py 管理员权限管理抽屉,支持查看、新增、编辑和删除权限组
components/user_manage.py 使用有效角色作为用户新增和编辑时的角色选项
components/core_side_menu.py 按当前用户有效访问规则过滤侧边菜单
views/core_pages/__init__.py 按当前用户有效访问规则过滤页面搜索并注入权限管理入口
callbacks/core_pages_c/__init__.py 处理右上角用户菜单中的“权限管理”入口点击事件
app.py 对受保护页面进行登录状态和页面访问规则校验
magic_init.py 创建内置权限分组表并展示创建记录

新增数据库模型

数据库模型放在models/目录。建议参考已有的users.pydepartments.pylogs.py组织模型字段和操作方法。

开发流程:

  1. models/下新增模型文件。
  2. magic_init.py的初始化表清单或自定义初始化逻辑中注册新表。models/init_db.py已废弃,仅保留为迁移提示入口;内置的用户权限分组表已在当前模板中注册。
  3. 运行python -m magic_init初始化或更新基础数据。
  4. 在页面回调中调用模型方法读写数据。

切换数据库类型时,先修改DatabaseConfig.database_type和对应连接配置,再确认目标数据库已创建。

内置模型会在PeeweeSQLAlchemySQLModel实现之间保持一致的上层类型契约。以登录日志为例,LoginLogs.add_log()login_datetime参数应传入原生datetime对象,LoginLogs.get_logs()返回记录中的login_datetime也始终为datetime对象。日期时间的字符串格式化应留在页面展示、文件导出或接口序列化层处理,不应放在模型层,以免不同ORM返回不同类型。

登录与安全能力

常见安全增强点:

  • 开启enable_login_captcha增加登录滑块验证。
  • 开启enable_email_login增加邮件验证码登录方式。
  • 开启enable_otp_login增加OTP动态口令登录方式。
  • 开启enable_duplicate_login_check辅助发现重复登录。
  • 开启enable_fullscreen_watermark为全屏场景增加用户水印。
  • 开启enable_login_rsa_crypto后,运行python -m magic_init生成RSA密钥对。

用户管理与账号资料维护

管理员可在“系统管理-用户管理”中新增、删除和编辑非管理员用户。新增用户时可填写用户名、密码、邮箱、所属部门和用户角色;编辑用户时,用户id和用户名保持只读,可调整邮箱、所属部门和用户角色。

用户角色选项来自硬编码配置与数据库权限组综合后的有效角色。用户邮箱允许为空,但非空邮箱必须唯一,主要用于邮件验证码登录;所属部门可以为空,选择部门时会校验目标部门是否仍然存在。为避免误操作,模板默认不允许在用户管理中编辑或删除管理员角色用户。

启用邮件验证码登录

邮件验证码登录在FlaskFastAPI后端变体中的配置和交互一致,默认处于关闭状态。完整启用流程如下:

  1. configs/base_config.py中设置BaseConfig.enable_email_login = True
  2. 按照配置参数填写configs/email_config.py中的SMTP参数。
  3. 运行python -m magic_init,确保用户表邮箱字段、邮箱唯一约束和邮件验证码表已经创建。
  4. 通过初始化提示或“系统管理-用户管理”为用户关联邮箱。
  5. 启动应用,从登录页“更多登录方式-邮箱验证登录”完成一次发送和登录测试。

用户邮箱允许为空,但所有非空邮箱必须唯一。只有已关联有效用户的邮箱才能获取验证码。初始化新数据库时,magic_init.py会询问可选的初始管理员邮箱;升级已有项目时,该命令会自动补充user_email字段、唯一索引和邮件验证码表。旧数据库中已有的failed_attempts列可以保留,升级后不再读写该列。若旧数据中存在重复的非空邮箱,需要先清理重复值,才能成功建立唯一约束。

验证码生命周期

  • 验证码是由安全随机数生成器产生的6位数字。
  • verification_code_expire_seconds只控制验证码有效期,默认300秒。
  • verification_code_resend_interval_seconds单独控制同一邮箱的重复发送等待,默认60秒,且不能超过验证码有效期。
  • 重复发送等待结束后可以获取新验证码;新验证码签发成功后旧验证码立即失效,并重新计算有效期。
  • 模板不累计验证码错误次数,输入错误不会锁定当前验证码;验证码仍受有效期和成功后单次消费约束。
  • 验证成功后验证码会立即消费;过期记录会在校验时清理。首次邮件发送失败会清理本次签发记录,重发邮件失败则恢复此前仍存在的验证码记录。
  • 成功或失败的验证码登录结果会写入登录日志;邮件验证码登录会建立普通非“记住我”会话。

enable_login_captcha只作用于用户名密码登录表单,不会自动应用到邮件验证码登录弹窗。模板当前按邮箱维度限制重复发送,不累计单个验证码的错误次数,也不包含按IP或全局维度的发送、校验限流;面向公网部署时,建议在反向代理、网关或邮件发送服务侧补充限流与监控。

相关实现文件

文件 作用
configs/base_config.py 控制是否展示并启用邮件验证码登录
configs/email_config.py 配置SMTP连接、发件人、验证码有效期和重复发送等待时间
models/users.py 维护唯一的用户邮箱并按邮箱查询用户
models/email_verifications.py 签发、限频、校验和消费验证码
utils/email_utils.py 校验邮件配置并发送纯文本及HTML验证码邮件
callbacks/login_c.py 处理验证码发送、倒计时、校验、登录和日志记录
magic_init.py 创建或兼容升级用户邮箱及验证码数据表结构

启用OTP动态口令登录

OTP动态口令登录在FlaskFastAPI后端变体中的配置和交互一致,默认处于关闭状态。完整启用流程如下:

  1. configs/base_config.py中设置BaseConfig.enable_otp_login = True
  2. 按照配置参数检查或调整configs/otp_config.py中的OTP参数。
  3. 运行python -m magic_init,确保OTP凭据表已经创建;已初始化过的旧数据库也会在启用或绑定时兼容创建该表。
  4. 启动应用,用户登录后从右上角用户菜单进入“OTP绑定”,使用认证器App扫码并输入当前6位动态口令完成绑定。
  5. 重新打开登录页,从“更多登录方式-OTP动态口令登录”输入用户名和认证器App中的6位动态口令完成登录测试。

OTP登录不要求输入密码,也不支持“记住我”表单状态;它会在校验成功后建立普通登录会话。未绑定OTP的用户无法通过该方式登录。用户重复绑定时会用新的二维码和共享密钥覆盖旧绑定。

OTP生命周期与安全行为

  • 口令固定为6位数字,默认每30秒刷新一次。
  • otp_valid_window默认允许前后各1个时间窗口,适合容忍轻微时钟偏差;安全要求较高时可以调小。
  • 口令成功使用后会记录对应时间窗口,防止同一动态口令重复使用。
  • 连续校验失败达到max_failed_attempts后,会临时锁定该用户的OTP登录lockout_seconds秒。
  • 成功、失败、锁定和口令失效等结果会写入登录日志,并在登录日志页面中以状态标签展示。
  • OTP共享密钥加密落库。生产环境建议设置独立的OtpConfig.secret_crypto_key,不要频繁更换;更换后旧凭据可能无法解密,需要用户重新绑定。

相关实现文件

文件 作用
configs/base_config.py 控制是否展示并启用OTP动态口令登录
configs/otp_config.py 配置OTP发行方、时间窗口、失败锁定和密钥加密材料
models/otp_credentials.py 保存用户OTP凭据、失败次数、锁定状态和已使用时间窗口
utils/otp_utils.py 生成共享密钥、构造扫码地址、加解密密钥和校验动态口令
components/otp_binding.py 处理用户扫码绑定和重新绑定流程
callbacks/login_c.py 处理OTP登录校验、会话建立和日志记录
magic_init.py 创建内置OTP凭据表并展示表创建记录

FlaskFastAPI后端变体

magic-dash-pro创建时可选择FlaskFastAPI后端。两种变体的上层页面、配置、模型和回调组织方式基本一致,主要差异集中在server.py、登录会话管理依赖和后端集成方式。

FastAPI接口文档

FastAPI后端变体可通过BaseConfig.enable_fastapi_docsBaseConfig.enable_fastapi_redoc分别启用Swagger UIReDoc,并通过fastapi_docs_pathnamefastapi_redoc_pathname设置独立访问路径。两种文档默认关闭;BaseConfig.fastapi_docs_offline默认为False,设置为True后,已启用的两种文档会统一使用模板内置离线静态资源。详细配置示例见配置参数BaseConfig.fastapi_docs_admin_only默认为True,启用后只有AuthConfig.admin_role对应的管理员可以访问文档页面、OAuth2 redirect及/openapi.json;未登录和非管理员请求分别返回401403。如需公开这些端点,可将该参数设置为False

接口文档直接注册在app.server上,不会创建第二个FastAPI实例。开发自定义接口时继续使用同一服务对象:

from server import app


@app.server.get("/api/health", tags=["系统"])
async def health_check():
    return {"status": "ok"}

启用接口文档并重启应用后,上述接口会自动出现在文档中。文档pathname必须是以/开头的静态路径,不能占用应用根路径、Dash内部接口、assets静态资源或其他已注册路由。server.py会在文件末尾调用utils/fastapi_docs.py,由该工具模块校验配置时已经存在的路由,并复用FastAPI原生处理函数将文档路由重新挂载到配置地址。后续注册的自定义接口仍会进入OpenAPI,但其pathname也应主动避开已配置的文档地址。

文档权限由server.py中的现有登录中间件统一处理,并根据server.docs_urlserver.redoc_urlserver.swagger_ui_oauth2_redirect_urlserver.openapi_url识别请求,因此自定义文档pathname不需要重复配置权限规则。管理员访问前应先通过/login建立登录cookie;接口文档端点不会把匿名请求重定向到登录页,而是返回语义明确的401响应。

在线模式下,文档页面沿用FastAPI默认配置,通过外部CDN加载Swagger UIReDoc资源。离线模式下,资源由Dash现有的assets静态文件路由提供;assets/fastapi-docs/已从Dash页面的自动JS/CSS注入扫描中排除,因此不会增加普通页面的资源负担或污染其样式。

Flask后端迁移到FastAPI后端时,可参考后端迁移指南

迁移后端变体时,建议新建目标变体项目,再迁移:

magic-dash create --name magic-dash-pro --backend fastapi

等价的简写形式为magic-dash create -n magic-dash-pro -b fastapi

  • views/
  • callbacks/
  • components/
  • 自定义models/
  • 自定义assets/
  • 业务配置项

静态资源与公共登录页资源

Dash会自动加载项目assets/目录下的静态资源。业务自定义样式、脚本、图片和图标可直接放入生成项目中的assets/目录,并使用/assets/...路径引用。

magic-dash-pro登录页默认使用以下公共静态资源:

assets/videos/login-bg.mp4
assets/imgs/login/gradient-bg.jpg
assets/imgs/login/gradient-bg-side.png

这些文件在magic-dash源码仓库中由magic_dash/public_assets/统一维护,内置magic-dash-promagic-dash-pro-fastapi模板目录默认不跟踪对应副本,以避免重复的大文件增大源码仓库和发布包体积。

通过magic-dash create --name magic-dash-pro创建项目时,CLI会自动把公共资源复制到新项目的assets/目录中,并在终端输出复制状态。生成项目中的这些文件属于项目静态资源,可以按业务需求替换或删除。

如果你在magic-dash源码仓库内直接开发内置模板,可使用以下命令恢复模板目录下的公共资源副本:

magic-dash init-assets

提交或发布前,可使用以下命令移除模板目录下的公共资源副本:

magic-dash remove-assets